Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-18.04
strategy:
fail-fast: false
max-parallel: 6
max-parallel: 7
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v1
Expand Down
8 changes: 8 additions & 0 deletions examples/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ pytest test_swag_labs.py --demo

<img src="https://seleniumbase.io/cdn/gif/swag_demo_3.gif" /><br />

Run a Wordle-solver example:

```bash
pytest wordle_test.py
```

<img src="https://seleniumbase.io/cdn/gif/wordle.gif" /><br />

Run an example test in Headless Mode: (invisible browser)

```bash
Expand Down
99 changes: 99 additions & 0 deletions examples/wordle_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import ast
import random
import requests
from seleniumbase import __version__
from seleniumbase import BaseCase


class WordleTests(BaseCase):

word_list = []

def initalize_word_list(self):
js_file = "https://www.powerlanguage.co.uk/wordle/main.e65ce0a5.js"
req_text = requests.get(js_file).text
start = req_text.find("var La=") + len("var La=")
end = req_text.find("],", start) + 1
word_string = req_text[start:end]
self.word_list = ast.literal_eval(word_string)

def modify_word_list(self, word, letter_status):
new_word_list = []
correct_letters = []
present_letters = []
for i in range(len(word)):
if letter_status[i] == "correct":
correct_letters.append(word[i])
for w in self.word_list:
if w[i] == word[i]:
new_word_list.append(w)
self.word_list = new_word_list
new_word_list = []
for i in range(len(word)):
if letter_status[i] == "present":
present_letters.append(word[i])
for w in self.word_list:
if word[i] in w and word[i] != w[i]:
new_word_list.append(w)
self.word_list = new_word_list
new_word_list = []
for i in range(len(word)):
if (
letter_status[i] == "absent"
and word[i] not in correct_letters
and word[i] not in present_letters
):
for w in self.word_list:
if word[i] not in w:
new_word_list.append(w)
self.word_list = new_word_list
new_word_list = []

def skip_if_incorrect_env(self):
if self.headless:
message = "This test doesn't run in headless mode!"
print(message)
self.skip(message)
version = [int(i) for i in __version__.split(".") if i.isdigit()]
if version < [2, 4, 0]:
message = "This test requires SeleniumBase 2.4.0 or newer!"
print(message)
self.skip(message)

def test_wordle(self):
self.skip_if_incorrect_env()
self.open("https://www.powerlanguage.co.uk/wordle/")
self.click("game-app::shadow game-modal::shadow game-icon")
self.initalize_word_list()
keyboard_base = "game-app::shadow game-keyboard::shadow "
word = random.choice(self.word_list)
total_attempts = 0
success = False
for attempt in range(6):
total_attempts += 1
word = random.choice(self.word_list)
letters = []
for letter in word:
letters.append(letter)
button = 'button[data-key="%s"]' % letter
self.click(keyboard_base + button)
button = 'button[data-key="↵"]'
self.click(keyboard_base + button)
self.sleep(1) # Time for the animation
row = 'game-app::shadow game-row[letters="%s"]::shadow ' % word
tile = row + "game-tile:nth-of-type(%s)"
letter_status = []
for i in range(1, 6):
letter_eval = self.get_attribute(tile % str(i), "evaluation")
letter_status.append(letter_eval)
if letter_status.count("correct") == 5:
success = True
break
self.word_list.remove(word)
self.modify_word_list(word, letter_status)

self.save_screenshot_to_logs()
print('\nWord: "%s"\nAttempts: %s' % (word.upper(), total_attempts))
if not success:
self.fail("Unable to solve for the correct word in 6 attempts!")
self.sleep(3)
4 changes: 2 additions & 2 deletions mkdocs_build/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
regex>=2021.11.10
regex>=2022.1.18
tqdm>=4.62.3
docutils==0.18.1
python-dateutil==2.8.2
Expand All @@ -14,7 +14,7 @@ click==8.0.3;python_version>="3.6"
zipp==3.7.0;python_version>="3.7"
readme-renderer==32.0
pymdown-extensions==9.1;python_version>="3.6"
importlib-metadata==4.10.0;python_version>="3.7"
importlib-metadata==4.10.1;python_version>="3.7"
bleach==4.1.0
jsmin==3.0.1;python_version>="3.6"
lunr==0.6.1;python_version>="3.6"
Expand Down
8 changes: 5 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ packaging>=21.3;python_version>="3.6"
setuptools>=44.1.1;python_version<"3.5"
setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"
setuptools>=59.6.0;python_version>="3.6" and python_version<"3.7"
setuptools>=60.3.1;python_version>="3.7"
setuptools>=60.5.0;python_version>="3.7"
setuptools-scm>=5.0.2;python_version<"3.6"
setuptools-scm>=6.3.2;python_version>="3.6"
setuptools-scm>=6.4.2;python_version>="3.6"
tomli>=1.2.2;python_version>="3.6" and python_version<"3.7"
tomli>=2.0.0;python_version>="3.7"
wheel>=0.37.1
Expand All @@ -34,6 +34,7 @@ requests==2.25.1;python_version>="3.5" and python_version<"3.6"
requests==2.27.1;python_version>="3.6"
nose==1.3.7
sniffio==1.2.0;python_version>="3.7"
h11==0.13.0;python_version>="3.7"
trio==0.19.0;python_version>="3.7"
trio-websocket==0.9.2;python_version>="3.7"
pyopenssl==21.0.0;python_version>="3.7"
Expand All @@ -49,7 +50,8 @@ filelock==3.2.1;python_version<"3.6"
filelock==3.4.1;python_version>="3.6" and python_version<"3.7"
filelock==3.4.2;python_version>="3.7"
fasteners==0.16;python_version<"3.5"
fasteners==0.16.3;python_version>="3.5"
fasteners==0.16.3;python_version>="3.5" and python_version<"3.6"
fasteners==0.17.2;python_version>="3.6"
execnet==1.9.0
pluggy==0.13.1;python_version<"3.6"
pluggy==1.0.0;python_version>="3.6"
Expand Down
7 changes: 7 additions & 0 deletions seleniumbase/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
from seleniumbase.__version__ import __version__ # noqa
from seleniumbase.core.browser_launcher import get_driver # noqa
from seleniumbase.fixtures.base_case import BaseCase # noqa
from seleniumbase.masterqa.master_qa import MasterQA # noqa
from seleniumbase.common import decorators # noqa
from seleniumbase.common import encryption # noqa
import collections
import sys

if sys.version_info[0] >= 3:
from seleniumbase import translate # noqa
if sys.version_info >= (3, 10):
collections.Callable = collections.abc.Callable # Lifeline for "nosetests"
del collections # Undo "import collections" / Simplify "dir(seleniumbase)"
del sys # Undo "import sys" / Simplify "dir(seleniumbase)"

version_info = [int(i) for i in __version__.split(".") if i.isdigit()] # noqa
2 changes: 1 addition & 1 deletion seleniumbase/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "2.3.14"
__version__ = "2.4.0"
7 changes: 5 additions & 2 deletions seleniumbase/core/report_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ def process_failures(test, test_count, browser_type, duration):
bad_page_image = "failure_%s.png" % test_count
bad_page_data = "failure_%s.txt" % test_count
screenshot_path = "%s/%s" % (LATEST_REPORT_DIR, bad_page_image)
with open(screenshot_path, "wb") as file:
file.write(test._last_page_screenshot)
if hasattr(test, "_last_page_screenshot"):
with open(screenshot_path, "wb") as file:
file.write(test._last_page_screenshot)
page_actions.save_test_failure_data(
test.driver, bad_page_data, browser_type, folder=LATEST_REPORT_DIR
)
Expand All @@ -69,6 +70,8 @@ def process_failures(test, test_count, browser_type, duration):
exc_message = sys.last_value
except Exception:
exc_message = "(Unknown Exception)"
if not hasattr(test, "_last_page_url"):
test._last_page_url = "about:blank"
return '"%s","%s","%s","%s","%s","%s","%s","%s","%s","%s"' % (
test_count,
"FAILED!",
Expand Down
50 changes: 49 additions & 1 deletion seleniumbase/fixtures/base_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,22 +834,30 @@ def open_if_not_url(self, url):
def is_element_present(self, selector, by=By.CSS_SELECTOR):
self.wait_for_ready_state_complete()
selector, by = self.__recalculate_selector(selector, by)
if self.__is_shadow_selector(selector):
return self.__is_shadow_element_present(selector)
return page_actions.is_element_present(self.driver, selector, by)

def is_element_visible(self, selector, by=By.CSS_SELECTOR):
self.wait_for_ready_state_complete()
selector, by = self.__recalculate_selector(selector, by)
if self.__is_shadow_selector(selector):
return self.__is_shadow_element_visible(selector)
return page_actions.is_element_visible(self.driver, selector, by)

def is_element_enabled(self, selector, by=By.CSS_SELECTOR):
self.wait_for_ready_state_complete()
selector, by = self.__recalculate_selector(selector, by)
if self.__is_shadow_selector(selector):
return self.__is_shadow_element_enabled(selector)
return page_actions.is_element_enabled(self.driver, selector, by)

def is_text_visible(self, text, selector="html", by=By.CSS_SELECTOR):
self.wait_for_ready_state_complete()
time.sleep(0.01)
selector, by = self.__recalculate_selector(selector, by)
if self.__is_shadow_selector(selector):
return self.__is_shadow_text_visible(text, selector)
return page_actions.is_text_visible(self.driver, text, selector, by)

def is_attribute_present(
Expand All @@ -860,6 +868,10 @@ def is_attribute_present(
self.wait_for_ready_state_complete()
time.sleep(0.01)
selector, by = self.__recalculate_selector(selector, by)
if self.__is_shadow_selector(selector):
return self.__is_shadow_attribute_present(
selector, attribute, value
)
return page_actions.is_attribute_present(
self.driver, selector, attribute, value, by
)
Expand Down Expand Up @@ -1259,6 +1271,8 @@ def get_attribute(
selector, by = self.__recalculate_selector(selector, by)
self.wait_for_ready_state_complete()
time.sleep(0.01)
if self.__is_shadow_selector(selector):
return self.__get_shadow_attribute(selector, attribute)
element = page_actions.wait_for_element_present(
self.driver, selector, by, timeout
)
Expand Down Expand Up @@ -5834,7 +5848,7 @@ def skip(self, reason=""):
self.__passed_then_skipped = True
self.__will_be_skipped = True
sb_config._results[test_id] = "Skipped"
if self.with_db_reporting:
if hasattr(self, "with_db_reporting") and self.with_db_reporting:
if self.is_pytest:
self.__skip_reason = reason
else:
Expand Down Expand Up @@ -6004,6 +6018,10 @@ def __get_shadow_text(self, selector):
element = self.__get_shadow_element(selector)
return element.text

def __get_shadow_attribute(self, selector, attribute):
element = self.__get_shadow_element(selector)
return element.get_attribute(attribute)

def __wait_for_shadow_text_visible(self, text, selector):
start_ms = time.time() * 1000.0
stop_ms = start_ms + (settings.SMALL_TIMEOUT * 1000.0)
Expand Down Expand Up @@ -6132,6 +6150,36 @@ def __is_shadow_element_visible(self, selector):
except Exception:
return False

def __is_shadow_element_enabled(self, selector):
try:
element = self.__get_shadow_element(selector, timeout=0.1)
return element.is_enabled()
except Exception:
return False

def __is_shadow_text_visible(self, text, selector):
try:
element = self.__get_shadow_element(selector, timeout=0.1)
return element.is_displayed() and text in element.text
except Exception:
return False

def __is_shadow_attribute_present(self, selector, attribute, value=None):
try:
element = self.__get_shadow_element(selector, timeout=0.1)
found_value = element.get_attribute(attribute)
if found_value is None:
return False
if value is not None:
if found_value == value:
return True
else:
return False
else:
return True
except Exception:
return False

def __wait_for_shadow_element_present(self, selector):
element = self.__get_shadow_element(selector)
return element
Expand Down
5 changes: 2 additions & 3 deletions seleniumbase/fixtures/page_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,12 @@ def is_attribute_present(
element = driver.find_element(by=by, value=selector)
found_value = element.get_attribute(attribute)
if found_value is None:
raise Exception()

return False
if value is not None:
if found_value == value:
return True
else:
raise Exception()
return False
else:
return True
except Exception:
Expand Down
8 changes: 5 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@
'setuptools>=44.1.1;python_version<"3.5"',
'setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"',
'setuptools>=59.6.0;python_version>="3.6" and python_version<"3.7"',
'setuptools>=60.3.1;python_version>="3.7"',
'setuptools>=60.5.0;python_version>="3.7"',
'setuptools-scm>=5.0.2;python_version<"3.6"',
'setuptools-scm>=6.3.2;python_version>="3.6"',
'setuptools-scm>=6.4.2;python_version>="3.6"',
'tomli>=1.2.2;python_version>="3.6" and python_version<"3.7"',
'tomli>=2.0.0;python_version>="3.7"',
"wheel>=0.37.1",
Expand All @@ -159,6 +159,7 @@
'requests==2.27.1;python_version>="3.6"',
"nose==1.3.7",
'sniffio==1.2.0;python_version>="3.7"',
'h11==0.13.0;python_version>="3.7"',
'trio==0.19.0;python_version>="3.7"',
'trio-websocket==0.9.2;python_version>="3.7"',
'pyopenssl==21.0.0;python_version>="3.7"',
Expand All @@ -174,7 +175,8 @@
'filelock==3.4.1;python_version>="3.6" and python_version<"3.7"',
'filelock==3.4.2;python_version>="3.7"',
'fasteners==0.16;python_version<"3.5"',
'fasteners==0.16.3;python_version>="3.5"',
'fasteners==0.16.3;python_version>="3.5" and python_version<"3.6"',
'fasteners==0.17.2;python_version>="3.6"',
"execnet==1.9.0",
'pluggy==0.13.1;python_version<"3.6"',
'pluggy==1.0.0;python_version>="3.6"',
Expand Down