Skip to content

Commit 5f69a8e

Browse files
authored
Merge pull request #845 from seleniumbase/shadow-dom-support-and-more
Add support for Shadow DOM interaction and more
2 parents f690ab9 + be1060c commit 5f69a8e

File tree

17 files changed

+478
-70
lines changed

17 files changed

+478
-70
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ ui_tests/
451451
├── __init__.py
452452
├── google_objects.py
453453
├── google_test.py
454+
├── sb_swag_test.py
454455
└── swag_labs_test.py
455456
```
456457
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
""" Classic Page Object Model with the "sb" fixture """
2+
3+
4+
class LoginPage():
5+
6+
def login_to_swag_labs(self, sb, username):
7+
sb.open("https://www.saucedemo.com/")
8+
sb.type("#user-name", username)
9+
sb.type("#password", "secret_sauce")
10+
sb.click('input[type="submit"]')
11+
12+
13+
class MyTests():
14+
15+
def test_swag_labs_login(self, sb):
16+
LoginPage().login_to_swag_labs(sb, "standard_user")
17+
sb.assert_element("#inventory_container")
18+
sb.assert_text("Products", "div.product_label")
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
""" Example test that uses the Page Object Model """
1+
""" Classic Page Object Model with BaseCase inheritance """
22

33
from seleniumbase import BaseCase
44

55

66
class LoginPage():
77

88
def login_to_swag_labs(self, sb, username):
9+
sb.open("https://www.saucedemo.com/")
910
sb.type("#user-name", username)
1011
sb.type("#password", "secret_sauce")
1112
sb.click('input[type="submit"]')
@@ -14,7 +15,6 @@ def login_to_swag_labs(self, sb, username):
1415
class MyTests(BaseCase):
1516

1617
def test_swag_labs_login(self):
17-
self.open("https://www.saucedemo.com/")
1818
LoginPage().login_to_swag_labs(self, "standard_user")
1919
self.assert_element("#inventory_container")
2020
self.assert_text("Products", "div.product_label")

examples/ip_cow_test.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

examples/test_shadow_dom.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
""" Shadow DOM test.
2+
First download files from PyPI.
3+
Then search for them on a multi-layered Shadow DOM page.
4+
This uses the "::shadow" selector for piercing shadow-root elements.
5+
Here's the URL that contains Shadow DOM: chrome://downloads/ """
6+
7+
8+
from seleniumbase import BaseCase
9+
10+
11+
class ShadowDomTests(BaseCase):
12+
13+
def download_tar_file_from_pypi(self, package):
14+
self.open("https://pypi.org/project/%s/#files" % package)
15+
pkg_header = self.get_text("h1.package-header__name").strip()
16+
pkg_name = pkg_header.replace(" ", "-")
17+
tar_file = pkg_name + ".tar.gz"
18+
tar_selector = 'div#files a[href$="%s"]' % tar_file
19+
self.click(tar_selector)
20+
return tar_file
21+
22+
def test_shadow_dom(self):
23+
if self.browser != "chrome":
24+
print("\n This test is for Google Chrome only!")
25+
self.skip('This test is for Google Chrome only!')
26+
if self.headless:
27+
print("\n This test does not run in headless mode!")
28+
self.skip('This test does not run in headless mode!')
29+
30+
file_name_1 = self.download_tar_file_from_pypi("sbase")
31+
file_name_2 = self.download_tar_file_from_pypi("tensorpy")
32+
33+
self.open("chrome://downloads/")
34+
search_icon = (
35+
"downloads-manager::shadow downloads-toolbar::shadow"
36+
" cr-toolbar::shadow cr-toolbar-search-field::shadow"
37+
" cr-icon-button")
38+
search_input = (
39+
"downloads-manager::shadow downloads-toolbar::shadow"
40+
" cr-toolbar::shadow cr-toolbar-search-field::shadow"
41+
" #searchInput")
42+
clear_search_icon = (
43+
"downloads-manager::shadow downloads-toolbar::shadow"
44+
" cr-toolbar::shadow cr-toolbar-search-field::shadow"
45+
" #clearSearch")
46+
file_link = (
47+
"downloads-manager::shadow #downloadsList"
48+
" downloads-item::shadow #file-link")
49+
remove_button = (
50+
"downloads-manager::shadow #downloadsList"
51+
" downloads-item::shadow #remove")
52+
no_downloads_area = "downloads-manager::shadow #no-downloads"
53+
54+
self.assert_element(search_icon)
55+
self.type(search_input, "sbase")
56+
self.assert_text(file_name_1, file_link)
57+
print("\n Download 1: %s" % self.get_text(file_link))
58+
self.type(search_input, "tensorpy")
59+
self.assert_text(file_name_2, file_link)
60+
print(" Download 2: %s" % self.get_text(file_link))
61+
self.click(clear_search_icon)
62+
self.click(remove_button)
63+
self.click(remove_button)
64+
self.assert_text("Files you download appear here", no_downloads_area)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from seleniumbase import BaseCase
2+
3+
4+
class MyTourClass(BaseCase):
5+
6+
def test_octocat_tour(self):
7+
self.maximize_window()
8+
self.open("https://seleniumbase.io/error_page/")
9+
self.wait_for_element("#parallax_octocat")
10+
self.create_tour(theme="bootstrap")
11+
self.add_tour_step("Welcome to the Octocat Tour!")
12+
self.add_tour_step("This is Octocat", "#parallax_octocat")
13+
self.add_tour_step("This is Octobi-Wan Catnobi", "#octobi_wan_catnobi")
14+
self.add_tour_step("<h1><b>Ooops!!!</b></h1>", "#parallax_error_text")
15+
self.add_tour_step("This is a Star Wars speeder.", "#speeder")
16+
self.add_tour_step("This is a sign with a 500-Error", "#parallax_sign")
17+
self.add_tour_step(
18+
"This is not the page you're looking for.", 'img[alt*="404"]')
19+
self.add_tour_step("<b>Have a great day!</b>", title="☀️ ☀️ ☀️ ☀️")
20+
self.add_tour_step("<b>And may the Force be with you!</b>", title="⭐")
21+
self.export_tour(filename="octocat_tour.js")
22+
self.play_tour()

help_docs/features_list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* Can handle Google Authenticator logins with [Python's one-time password library](https://pyotp.readthedocs.io/en/latest/).
3333
* Is backwards-compatible with Python [WebDriver](https://www.selenium.dev/projects/) methods. (Use: ``self.driver``)
3434
* Can execute JavaScript code from Python calls. (Use: ``self.execute_script()``)
35+
* Can pierce through Shadow DOM selectors. (Add ``::shadow`` to CSS fragments.)
3536
* Includes integrations for [MySQL](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/core/testcase_manager.py), [Selenium Grid](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/utilities/selenium_grid), [Azure](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/azure/jenkins/ReadMe.md), [GCP](https://github.com/seleniumbase/SeleniumBase/tree/master/integrations/google_cloud/ReadMe.md), [AWS](https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/plugins/s3_logging_plugin.py), and [Docker](https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/docker/ReadMe.md).
3637
* Includes a tool for [converting Selenium IDE recordings](https://github.com/seleniumbase/SeleniumBase/tree/master/seleniumbase/utilities/selenium_ide) into SeleniumBase scripts.
3738
* Can load and make assertions on PDF files from websites or the local file system.

help_docs/method_summary.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ self.load_html_file(html_file, new_page=True)
157157

158158
self.open_html_file(html_file)
159159

160-
self.execute_script(script)
160+
self.execute_script(script, *args, **kwargs)
161161

162162
self.execute_async_script(script, timeout=None)
163163

164-
self.safe_execute_script(script)
164+
self.safe_execute_script(script, *args, **kwargs)
165165

166166
self.set_window_rect(x, y, width, height)
167167

help_docs/syntax_formats.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -116,46 +116,46 @@ With SeleniumBase, you can use Page Objects to break out code from tests, but re
116116
```python
117117
from seleniumbase import BaseCase
118118

119-
class DataPage():
119+
class LoginPage():
120120

121-
def go_to_data_url(self, sb):
122-
sb.open("data:text/html,<p>Hello!</p><input />")
121+
def login_to_swag_labs(self, sb, username):
122+
sb.open("https://www.saucedemo.com/")
123+
sb.type("#user-name", username)
124+
sb.type("#password", "secret_sauce")
125+
sb.click('input[type="submit"]')
123126

124-
def add_input_text(self, sb, text):
125-
sb.type("input", text)
127+
class MyTests(BaseCase):
126128

127-
class ObjTests(BaseCase):
128-
129-
def test_data_url_page(self):
130-
DataPage().go_to_data_url(self)
131-
self.assert_text("Hello!", "p")
132-
DataPage().add_input_text(self, "Goodbye!")
129+
def test_swag_labs_login(self):
130+
LoginPage().login_to_swag_labs(self, "standard_user")
131+
self.assert_element("#inventory_container")
132+
self.assert_text("Products", "div.product_label")
133133
```
134134

135-
(See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/classic_obj_test.py">examples/boilerplates/classic_obj_test.py</a> for the full test.)
135+
(See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/samples/swag_labs_test.py">examples/boilerplates/samples/swag_labs_test.py</a> for the full test.)
136136

137137
<h3><img src="https://seleniumbase.io/img/green_logo.png" title="SeleniumBase" width="32" /> 6. The classic Page Object Model with the <code>sb</code> pytest fixture</h3>
138138

139139
This is similar to the classic Page Object Model with <code>BaseCase</code> inheritance, except that this time we pass the <code>sb</code> pytest fixture from the test into the <code>sb</code> arg of the page object class method, (instead of passing <code>self</code>). Now that you're using <code>sb</code> as a pytest fixture, you no longer need to import <code>BaseCase</code> anywhere in your code. See the example below:
140140

141141
```python
142-
class DataPage():
143-
144-
def go_to_data_url(self, sb):
145-
sb.open("data:text/html,<p>Hello!</p><input />")
142+
class LoginPage():
146143

147-
def add_input_text(self, sb, text):
148-
sb.type("input", text)
144+
def login_to_swag_labs(self, sb, username):
145+
sb.open("https://www.saucedemo.com/")
146+
sb.type("#user-name", username)
147+
sb.type("#password", "secret_sauce")
148+
sb.click('input[type="submit"]')
149149

150-
class ObjTests():
150+
class MyTests():
151151

152-
def test_data_url_page(self, sb):
153-
DataPage().go_to_data_url(sb)
154-
sb.assert_text("Hello!", "p")
155-
DataPage().add_input_text(sb, "Goodbye!")
152+
def test_swag_labs_login(self, sb):
153+
LoginPage().login_to_swag_labs(sb, "standard_user")
154+
sb.assert_element("#inventory_container")
155+
sb.assert_text("Products", "div.product_label")
156156
```
157157

158-
(See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/sb_fixture_test.py">examples/boilerplates/sb_fixture_test.py</a> for the full test.)
158+
(See <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/boilerplates/samples/sb_swag_test.py">examples/boilerplates/samples/sb_swag_test.py</a> for the full test.)
159159

160160
<h3><img src="https://seleniumbase.io/img/green_logo.png" title="SeleniumBase" width="32" /> 7. Using the <code>request</code> fixture to get the <code>sb</code> fixture (no class)</h3>
161161

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pip>=21.0.1;python_version>="3.6"
33
packaging>=20.9
44
setuptools>=44.1.1;python_version<"3.5"
55
setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"
6-
setuptools>=54.1.2;python_version>="3.6"
6+
setuptools>=54.2.0;python_version>="3.6"
77
setuptools-scm==5.0.2;python_version<"3.6"
88
setuptools-scm>=6.0.1;python_version>="3.6"
99
wheel>=0.36.2
@@ -47,7 +47,7 @@ pytest-xdist==2.2.1;python_version>="3.5"
4747
parameterized==0.8.1
4848
soupsieve==1.9.6;python_version<"3.5"
4949
soupsieve==2.0.1;python_version>="3.5" and python_version<"3.6"
50-
soupsieve==2.2;python_version>="3.6"
50+
soupsieve==2.2.1;python_version>="3.6"
5151
beautifulsoup4==4.9.3
5252
cryptography==2.9.2;python_version<"3.5"
5353
cryptography==3.0;python_version>="3.5" and python_version<"3.6"
@@ -59,7 +59,7 @@ pygments==2.8.1;python_version>="3.5"
5959
traitlets==4.3.3;python_version<"3.7"
6060
traitlets==5.0.5;python_version>="3.7"
6161
prompt-toolkit==1.0.18;python_version<"3.6"
62-
prompt-toolkit==3.0.17;python_version>="3.6"
62+
prompt-toolkit==3.0.18;python_version>="3.6"
6363
ipython==5.10.0;python_version<"3.5"
6464
ipython==6.5.0;python_version>="3.5" and python_version<"3.6"
6565
ipython==7.16.1;python_version>="3.6" and python_version<"3.7"

0 commit comments

Comments
 (0)