Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using chatgpt #70

Merged
merged 4 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The scarlet ibis, sometimes called red ibis (Eudocimus ruber), is a species of i
- [Tutorial](#Tutorial)<br>
- [Using other Web Drivers](#Using-other-Web-Drivers)<br>
- [Asynchronous execution](#Asynchronous-execution)<br>
- [ChatGPT assistance](#ChatGPT-assistance)<br>
- [Contributing](#Contributing)<br>

# Syntax
Expand Down Expand Up @@ -185,6 +186,8 @@ It is possible to run Guara using other Web Drivers like [Caqui](https://github.
# Asynchronous execution
The core code was extended to allow asynchronous executions. Get more details [here](https://github.com/douglasdcm/guara/tree/main/docs/ASYNC.md)

# ChatGPT assistance
It is possible to use [ChatGPT](https://chatgpt.com/) to help you organize your code in `Page Transactions` pattern. Check these [simple steps](https://github.com/douglasdcm/guara/blob/main/docs/CHATGPT_ASSISTANCE.md).

# How you can help?

Expand Down
284 changes: 284 additions & 0 deletions docs/CHATGPT_ASSISTANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
# Using ChatGPT with Page Transactions

As `Page Transactions` pattern is a new term. ChatGPT will not find a reference to it. But, it is not an excuse to not use the pattern. Teaching ChatGPT about the pattern is very easy and fast and the result is pretty much accurate.

1. Go to [ChatGPT](https://chatgpt.com/) page and login with your account. Create a new one if necessary.
2. Go to [Guara README.md](https://github.com/douglasdcm/guara/blob/main/README.md) and copy the raw content ![alt text](images/copy_raw_content.png)
3. Paste the following statements in the ChatGPT prompt
```
Given the explanation of the pattern Page Transactions after the dots let me know what you understood
...
<the raw content you copied>
```
4. Check it understanding and continue
5. Now that ChatGPT knows about the new pattern. Ask it to organize your code using it
```
Based on your understanding about the pattern Page Transactions organize the code after the dots
...
<your raw code>
```
6. Check if

- `guara` is part of the imports
- `Application` is used to orchestrate the code execution with `at` and `asserts`
7. Make necessary adjustments if necessary
8. Done! Your code is in the new pattern.

# Here is a practical example

## Raw code in Selenium

```python
import random
import pathlib
from selenium import webdriver
from selenium.webdriver.common.by import By


def test_sample_page():
file_path = pathlib.Path(__file__).parent.resolve()
driver = webdriver.Chrome()

driver.get(f"file:///{file_path}/sample.html")

title = driver.title
# assert title == "Web form"
assert title == "Sample page"

driver.implicitly_wait(0.5)

# text_box = driver.find_element(by=By.NAME, value="input")
text_box = driver.find_element(by=By.ID, value="input")
submit_button = driver.find_element(by=By.CSS_SELECTOR, value="button")

text = ["cheese", "selenium", "test", "bla", "foo"]
text_box.send_keys(text[random.randrange(len(text))])
from_input = text_box.get_attribute("value")
submit_button.click()

message = driver.find_element(by=By.ID, value="result")
value = message.text
# assert value == "It workls! Selenium!"
assert value == f"It works! {from_input}!"

driver.quit()
```

## It is the output of ChatGPT

To organize this code following the Page Transactions pattern, we need to abstract transactions (e.g., opening the page, interacting with input fields, and validating results) into reusable classes that inherit from AbstractTransaction. The test logic should then use the Application class to orchestrate these transactions.

Here's the organized code:

```python
import random
import pathlib
from selenium import webdriver
from selenium.webdriver.common.by import By
from guara.transaction import AbstractTransaction, Application
from guara import it, setup


# Transaction: Open the sample page
class OpenSamplePage(AbstractTransaction):
def __init__(self, driver, file_path):
super().__init__(driver)
self.file_path = file_path

def do(self, **kwargs):
self._driver.get(f"file:///{self.file_path}/sample.html")
self._driver.implicitly_wait(0.5)
return self._driver.title


# Transaction: Submit text to the form
class SubmitText(AbstractTransaction):
def do(self, **kwargs):
text = kwargs.get("text", "")
text_box = self._driver.find_element(by=By.ID, value="input")
submit_button = self._driver.find_element(by=By.CSS_SELECTOR, value="button")

text_box.send_keys(text)
from_input = text_box.get_attribute("value")
submit_button.click()
return from_input


# Transaction: Validate the result message
class ValidateResult(AbstractTransaction):
def do(self, **kwargs):
expected_text = kwargs.get("expected_text", "")
message = self._driver.find_element(by=By.ID, value="result")
return message.text == f"It works! {expected_text}!"


# Test Case
def test_sample_page():
file_path = pathlib.Path(__file__).parent.resolve()
driver = webdriver.Chrome()

# Instantiate the Application
app = Application(driver)

# Open the sample page and assert the title
title = app.at(OpenSamplePage, file_path=file_path).result
app.asserts(it.IsEqualTo, "Sample page")

# Submit text and validate the result
text_choices = ["cheese", "selenium", "test", "bla", "foo"]
selected_text = random.choice(text_choices)

app.at(SubmitText, text=selected_text)
app.at(ValidateResult, expected_text=selected_text).asserts(it.IsTrue)

# Close the application
app.at(setup.CloseApp)

```

Explanation:

Transactions:
OpenSamplePage: Handles the logic to open the web page and optionally returns the page title.
SubmitText: Interacts with the input field and submits the form, returning the entered text.
ValidateResult: Validates that the result message matches the expected format.

Application:
Orchestrates the transactions using the at method and applies assertions using the asserts method.

Test Flow:
The test script uses Application to execute transactions in a logical sequence.
Assertions are modular, readable, and reusable.

Reusability:
The transaction classes are reusable for other tests involving similar interactions. This modular approach reduces duplication and enhances maintainability.

Let me know if you'd like further adjustments!

## Make necessary adjustments

Notice the class `OpenSamplePage` has the parameter `file_path` with is not following the functional usage of the pattern and the assertion `it.IsTrue` does not exist in Guara core (yet). The new assertion introduced by the AI was a good induction.

It is still possible to use ChatGPT to fix the issues. For example:

```
The code has some inconsistencies listed after the dots. Based on the list, give corrections in the code and outputs it to me
....
- The transaction constructors does not allow other parameters than the `driver`. Any necessary parameters should be placed in the 'do' method
- The module 'gura.it' has no function 'IsTrue'. Check the explanation after the slashes to know how to build a new custom assertion 'IsTrue' and replace 'it.IsTrue' by 'IsTrue'

\\\\\
# Got from https://github.com/douglasdcm/guara/blob/main/docs/TUTORIAL.md#extending-assertions

The strategies to assert the result can e extended by inheriting from IAssertion as following:

# The code is the same of the previous session.
# The unnecessary details were not duplicated
# import IAssertion
from guara.transaction import Application, IAssertion

# Implements a new assertion to check variations of the expected value
class IsEqualToVariationsOf(IAssertion):
def __init__(self):
super().__init__()

def asserts(self, actual, expected):
if actual.lower() == expected.lower():
return True
raises AssertionError

def test_google_search(setup_app):
app = setup_app

# The new assertion is used to check variations of 'All', like 'aLL' or 'ALL'
app.at(home.Search, text="guara").asserts(IsEqualToVariationsOf, "All")
```

### The output

Here is the corrected and updated code based on the feedback:
Updates:

Transaction Constructors: Now accept only the driver parameter. Any additional parameters are passed to the do method.
Custom Assertion (IsTrue): A new custom assertion class is implemented, inheriting from IAssertion. This replaces the invalid it.IsTrue.

Corrected Code:

```python
import random
import pathlib
from selenium import webdriver
from selenium.webdriver.common.by import By
from guara.transaction import AbstractTransaction, Application, IAssertion
from guara import it, setup


# Custom Assertion: Checks if the actual value is True
class IsTrue(IAssertion):
def __init__(self):
super().__init__()

def asserts(self, actual, expected=None):
if actual is not True:
raise AssertionError(f"Expected True but got {actual}")


# Transaction: Open the sample page
class OpenSamplePage(AbstractTransaction):
def do(self, **kwargs):
file_path = kwargs.get("file_path")
self._driver.get(f"file:///{file_path}/sample.html")
self._driver.implicitly_wait(0.5)
return self._driver.title


# Transaction: Submit text to the form
class SubmitText(AbstractTransaction):
def do(self, **kwargs):
text = kwargs.get("text", "")
text_box = self._driver.find_element(by=By.ID, value="input")
submit_button = self._driver.find_element(by=By.CSS_SELECTOR, value="button")

text_box.send_keys(text)
from_input = text_box.get_attribute("value")
submit_button.click()
return from_input


# Transaction: Validate the result message
class ValidateResult(AbstractTransaction):
def do(self, **kwargs):
expected_text = kwargs.get("expected_text", "")
message = self._driver.find_element(by=By.ID, value="result")
actual_text = message.text
return actual_text == f"It works! {expected_text}!"


# Test Case
def test_sample_page():
file_path = pathlib.Path(__file__).parent.resolve()
driver = webdriver.Chrome()

# Instantiate the Application
app = Application(driver)

# Open the sample page and assert the title
title = app.at(OpenSamplePage, file_path=str(file_path)).result
app.asserts(it.IsEqualTo, "Sample page")

# Submit text and validate the result
text_choices = ["cheese", "selenium", "test", "bla", "foo"]
selected_text = random.choice(text_choices)

app.at(SubmitText, text=selected_text)
result = app.at(ValidateResult, expected_text=selected_text).result

# Use the custom assertion to validate that the result is True
app.asserts(IsTrue, result)

# Close the application
app.at(setup.CloseApp)
```

# Conclusion
The code needs some polishiment, like the `title` variable is never used, the occurrences of `app.asserts` can be suppressed to a single line like `app.at(OpenSamplePage, file_path=str(file_path)).asserts(it.IsEqualTo, "Sample page")` and the transaction `ValidateResult` does not seems to be a valid transaction the user does in the application, so this code could be merged with `SubmitText` to reduce the verbosity. However, despite these embellishments, the code works perfectly.
Binary file added docs/images/copy_raw_content.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 12 additions & 42 deletions guara/asynchronous/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,47 +117,17 @@ async def perform(self) -> "Application":
Returns:
(Application)
"""
for index in range(0, len(self._coroutines), 1):
(
await self.get_assertion(index)
if not await self.get_transaction(index)
else None
)
for coroutine in self._coroutines:
if coroutine.get(self._TRANSACTION):
LOGGER.info(f"Transaction '{self._transaction_name}'")
for k, v in self._kwargs.items():
LOGGER.info(f" {k}: {v}")
self._result = await coroutine.get(self._TRANSACTION)
continue

LOGGER.info(f"Assertion '{self._it.__name__}'")
LOGGER.info(f" actual: '{self._result}'")
LOGGER.info(f" expected: '{self._expected}'")
await coroutine.get(self._ASSERTION)
self._coroutines.clear()
return self

async def get_transaction(self, index: int) -> Coroutine[None, None, bool]:
"""
Retrieving the transaction from the coroutine.

Args:
index: (int): The index of the current coroutine.

Returns:
(Coroutine[None, None, bool])
"""
transaction: Coroutine[None, None, Any] = self._coroutines[index].get(
self._TRANSACTION
)
if transaction:
LOGGER.info(f"Transaction: {self._transaction_name}")
for key, value in self._kwargs.items():
LOGGER.info(f" {key}: {value}")
self._result = await transaction
return True
return False

async def get_assertion(self, index: int) -> Coroutine[None, None, None]:
"""
Retrieving the assertion from the coroutine.

Args:
index: (int): The index of the current coroutine.

Returns:
(Coroutine[None, None, None])
"""
LOGGER.info(f"Assertion: {self._it.__name__}")
LOGGER.info(f" Actual : {self._result}")
LOGGER.info(f" Expected: {self._expected}")
return self._coroutines[index].get(self._ASSERTION)
Loading