├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── dependency-review.yml
├── .pytest.ini
├── LICENSE.md
├── MANIFEST.in
├── README.md
├── assets
└── CapSolver Ads.png
├── dev
├── README.md
├── assets
│ ├── bypass_turnstile.mp4
│ ├── events_mouse.png
│ ├── events_mousepad.png
│ ├── heatmap.png
│ ├── heatmap_biased.png
│ ├── mouse_path_gen.png
│ ├── mousemove_events_gen.png
│ ├── mousemove_events_test_sample_based.png
│ ├── mousemove_events_test_samples_based.png
│ └── real_mouse_path.png
├── elem_midpoint_calc.py
├── human_like_path.py
├── show_mousemove.py
├── target_interception.py
└── viewport_intersection.py
├── docs
├── .buildinfo
├── .doctrees
│ ├── api
│ │ ├── By.doctree
│ │ ├── Chrome.doctree
│ │ ├── ChromeOptions.doctree
│ │ ├── Context.doctree
│ │ ├── Input.doctree
│ │ ├── RequestInterception.doctree
│ │ ├── Target.doctree
│ │ └── WebELement.doctree
│ ├── environment.pickle
│ └── index.doctree
├── .nojekyll
├── _modules
│ ├── index.html
│ └── selenium_driverless
│ │ ├── input
│ │ ├── pointer.html
│ │ ├── pointer
│ │ │ └── index.html
│ │ └── utils.html
│ │ ├── scripts
│ │ ├── network_interceptor.html
│ │ ├── network_interceptor
│ │ │ └── index.html
│ │ ├── switch_to.html
│ │ └── switch_to
│ │ │ └── index.html
│ │ ├── types
│ │ ├── base_target.html
│ │ ├── base_target
│ │ │ └── index.html
│ │ ├── by.html
│ │ ├── by
│ │ │ └── index.html
│ │ ├── context.html
│ │ ├── context
│ │ │ └── index.html
│ │ ├── options.html
│ │ ├── options
│ │ │ └── index.html
│ │ ├── target.html
│ │ ├── target
│ │ │ └── index.html
│ │ ├── webelement.html
│ │ └── webelement
│ │ │ └── index.html
│ │ ├── webdriver.html
│ │ └── webdriver
│ │ └── index.html
├── _sources
│ ├── api
│ │ ├── By.rst.txt
│ │ ├── Chrome.rst.txt
│ │ ├── ChromeOptions.rst.txt
│ │ ├── Context.rst.txt
│ │ ├── Input.rst.txt
│ │ ├── RequestInterception.rst.txt
│ │ ├── Target.rst.txt
│ │ └── WebELement.rst.txt
│ └── index.rst.txt
├── _static
│ ├── _sphinx_javascript_frameworks_compat.js
│ ├── basic.css
│ ├── css
│ │ ├── badge_only.css
│ │ ├── fonts
│ │ │ ├── Roboto-Slab-Bold.woff
│ │ │ ├── Roboto-Slab-Bold.woff2
│ │ │ ├── Roboto-Slab-Regular.woff
│ │ │ ├── Roboto-Slab-Regular.woff2
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ ├── fontawesome-webfont.woff2
│ │ │ ├── lato-bold-italic.woff
│ │ │ ├── lato-bold-italic.woff2
│ │ │ ├── lato-bold.woff
│ │ │ ├── lato-bold.woff2
│ │ │ ├── lato-normal-italic.woff
│ │ │ ├── lato-normal-italic.woff2
│ │ │ ├── lato-normal.woff
│ │ │ └── lato-normal.woff2
│ │ └── theme.css
│ ├── doctools.js
│ ├── documentation_options.js
│ ├── file.png
│ ├── jquery.js
│ ├── js
│ │ ├── badge_only.js
│ │ ├── html5shiv-printshiv.min.js
│ │ ├── html5shiv.min.js
│ │ └── theme.js
│ ├── language_data.js
│ ├── minus.png
│ ├── plus.png
│ ├── pygments.css
│ ├── searchtools.js
│ └── sphinx_highlight.js
├── api
│ ├── By.html
│ ├── By
│ │ └── index.html
│ ├── Chrome.html
│ ├── Chrome
│ │ └── index.html
│ ├── ChromeOptions.html
│ ├── ChromeOptions
│ │ └── index.html
│ ├── Context.html
│ ├── Context
│ │ └── index.html
│ ├── Input.html
│ ├── Input
│ │ └── index.html
│ ├── RequestInterception.html
│ ├── RequestInterception
│ │ └── index.html
│ ├── Target.html
│ ├── Target
│ │ └── index.html
│ ├── WebELement.html
│ └── WebELement
│ │ └── index.html
├── genindex.html
├── genindex
│ └── index.html
├── index.html
├── objects.inv
├── py-modindex
│ └── index.html
├── search.html
├── search
│ └── index.html
└── searchindex.js
├── docs_source
├── api
│ ├── By.rst
│ ├── Chrome.rst
│ ├── ChromeOptions.rst
│ ├── Context.rst
│ ├── Input.rst
│ ├── RequestInterception.rst
│ ├── Target.rst
│ ├── WebELement.rst
│ └── files
│ │ └── request_interception.py
├── conf.py
└── index.rst
├── examples
└── proxy_with_auth.py
├── main.py
├── pyproject.toml
├── requirements.txt
├── setup.cfg
├── setup.py
├── src
└── selenium_driverless
│ ├── __init__.py
│ ├── files
│ ├── __init__.py
│ ├── js
│ │ └── show_mousemove.js
│ └── mv3_extension
│ │ ├── driverless_background_mv3_243ffdd55e32a012b4f253b2879af978.js
│ │ └── manifest.json
│ ├── input
│ ├── __init__.py
│ ├── pointer.py
│ └── utils.py
│ ├── scripts
│ ├── __init__.py
│ ├── driver_utils.py
│ ├── geometry.py
│ ├── network_interceptor.py
│ ├── prefs.py
│ └── switch_to.py
│ ├── sync
│ ├── __init__.py
│ ├── alert.py
│ ├── base_target.py
│ ├── context.py
│ ├── pointer.py
│ ├── switch_to.py
│ ├── target.py
│ ├── webdriver.py
│ └── webelement.py
│ ├── types
│ ├── __init__.py
│ ├── alert.py
│ ├── base_target.py
│ ├── by.py
│ ├── context.py
│ ├── deserialize.py
│ ├── options.py
│ ├── target.py
│ └── webelement.py
│ ├── utils
│ ├── __init__.py
│ └── utils.py
│ └── webdriver.py
├── sync_main.py
└── tests
├── antibots
├── test_bet365.py
├── test_brotector.py
├── test_cloudfare.py
└── test_selenium_detector.py
├── assets
├── bundle.js
├── clean.json
└── index.html
├── conftest.py
├── fp.py
├── html
├── test_elem_cords.py
├── test_html_source.py
└── test_relative_find_elem.py
├── interaction
├── test_mouse.py
├── test_select.py
└── test_send_keys.py
├── javascript
└── test_isolated_context.py
├── network
├── test_auth.py
└── test_single_requests.py
├── pages
└── test_cookies.py
├── server_for_testing.py
├── sync
└── test_sync_selenium_detector.py
└── test_other.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [kaliiiiiiiiii] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: kaliiii # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 |
7 | version: 2
8 | updates:
9 | - package-ecosystem: "" # See documentation for possible values
10 | directory: "/" # Location of package manifests
11 | schedule:
12 | interval: "weekly"
13 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
4 | #
5 | # Source repository: https://github.com/actions/dependency-review-action
6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
7 | name: 'Dependency Review'
8 | on: [pull_request]
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | dependency-review:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: 'Checkout Repository'
18 | uses: actions/checkout@v3
19 | - name: 'Dependency Review'
20 | uses: actions/dependency-review-action@v3
21 |
--------------------------------------------------------------------------------
/.pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | testpaths =
3 | tests
4 | markers =
5 | skip_offline: skips test if machine is offline
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ## License
2 |
3 | [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa]
4 |
5 | This work is licensed under a
6 | [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa] with an **addition for `Section 1(k)`** in the [LEGAL CODE](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.en):
7 |
8 | > Commercial means primarily intended for or directed
9 | > towards commercial advantage or monetary compensation. \
10 | > A business, project or public agreement with a commercial intent of any kind
11 | > which profits more than, or equal to 7'000 US-Dollar per month,
12 | > or any monetary equivalent to that, is not subject to this definition
13 | > of NonCommercial.
14 |
15 | ---
16 | If you wish to **use this project commercially**, you can contact the Author for a custom License.
17 | This usually includes a **fee** of around **5-6%** based on your current profit.
18 |
19 | ## Disclaimer
20 | This project is meant for **educational purposes only**. Use it responsibly. \
21 | **The Author** does **not provide any warranty** and is **not liable** in any way for what or how it gets used.
22 |
23 | ## Copyright and Author
24 |
25 | [Aurin Aegerter](mailto:aurin.aegerter@stud.gymthun.ch) (aka **Steve**)
26 |
27 | [cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/
28 | [cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png
29 | [cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg
30 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include pyproject.toml
2 | include *.md
3 | include LICENSE.md
4 | include src/selenium_driverless/files/js/*
5 | include src/selenium_driverless/files/mv3_extension/*
6 | prune tests
7 | prune .github
8 | prune dev
9 | prune docs
10 | prune docs_source
11 | prune examples
12 | exclude build_upload.md
13 | exclude main.py
14 | exclude sync_main.py
15 |
--------------------------------------------------------------------------------
/assets/CapSolver Ads.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/assets/CapSolver Ads.png
--------------------------------------------------------------------------------
/dev/README.md:
--------------------------------------------------------------------------------
1 | ## Page Interactions
2 |
3 |
4 | ### element click
5 |
6 | default
7 | 
8 |
9 | `bias_a = 0.7`
10 | 
11 |
12 |
13 | ### mouse path
14 |
15 | #### generated example
16 | 
17 | 
18 |
19 | #### test in Browser based on generated path
20 | 
21 | 
22 |
23 | #### real example
24 | - with [mouse event testing](https://www.vsynctester.com/testing/mouse.html)
25 | - mousepad
26 | - Windows Laptop
27 |
28 | => events of almost exactly 60Hz (screen-frequency)
29 |
30 | 
31 |
32 | - with [getCoalescedEvents demo](https://omwnk.csb.app/)
33 | - gets more than 60 events/sec with `getCoalescedEvents` api
34 | - about 2-2.1 Coalesced Event per normal event
35 |
36 | => about. 180 events/sec
37 |
38 | - with mousepad
39 | 
40 | - with mouse
41 | 
42 |
43 | #### bypass turnstile
44 | https://github.com/kaliiiiiiiiii/Selenium-Driverless/assets/89038706/04bcc39b-0233-448e-80db-906f5b89f086
45 |
46 |
47 |
--------------------------------------------------------------------------------
/dev/assets/bypass_turnstile.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/bypass_turnstile.mp4
--------------------------------------------------------------------------------
/dev/assets/events_mouse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/events_mouse.png
--------------------------------------------------------------------------------
/dev/assets/events_mousepad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/events_mousepad.png
--------------------------------------------------------------------------------
/dev/assets/heatmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/heatmap.png
--------------------------------------------------------------------------------
/dev/assets/heatmap_biased.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/heatmap_biased.png
--------------------------------------------------------------------------------
/dev/assets/mouse_path_gen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/mouse_path_gen.png
--------------------------------------------------------------------------------
/dev/assets/mousemove_events_gen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/mousemove_events_gen.png
--------------------------------------------------------------------------------
/dev/assets/mousemove_events_test_sample_based.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/mousemove_events_test_sample_based.png
--------------------------------------------------------------------------------
/dev/assets/mousemove_events_test_samples_based.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/mousemove_events_test_samples_based.png
--------------------------------------------------------------------------------
/dev/assets/real_mouse_path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/dev/assets/real_mouse_path.png
--------------------------------------------------------------------------------
/dev/elem_midpoint_calc.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from scipy.interpolate import griddata
4 | import time
5 |
6 | from selenium_driverless.scripts.geometry import rand_mid_loc
7 |
8 |
9 | def rotate(point, angle, center):
10 | x, y = point
11 | cx, cy = center
12 | rotated_x = (x - cx) * np.cos(angle) - (y - cy) * np.sin(angle) + cx
13 | rotated_y = (x - cx) * np.sin(angle) + (y - cy) * np.cos(angle) + cy
14 | return [rotated_x, rotated_y]
15 |
16 |
17 | if __name__ == "__main__":
18 | elem = [
19 | (300, 200), # A
20 | (400, 200), # B
21 | (400, 400), # C
22 | (300, 400) # D
23 | ]
24 | elem_angle = np.radians(30) # Center of the rectangle
25 | elem = [rotate(point, elem_angle, elem[0]) for point in elem]
26 |
27 | n = 100_000 # Number of random points
28 | spread_a = 1 # Bias for a
29 | spread_b = 1 # Bias for b
30 | bias_a = 0.5
31 | bias_b = 0.5
32 | border = 0.05
33 |
34 | # create grid
35 | x_grid = np.linspace(min(point[0] for point in elem),
36 | max(point[0] for point in elem), 100)
37 | y_grid = np.linspace(min(point[1] for point in elem),
38 | max(point[1] for point in elem), 100)
39 | x_grid, y_grid = np.meshgrid(x_grid, y_grid)
40 | z_values = np.zeros_like(x_grid)
41 |
42 | start_time = time.perf_counter()
43 | for _ in range(n):
44 | result_point = rand_mid_loc(elem, spread_a, spread_b, bias_a, bias_b, border)
45 | x_idx = np.argmin(np.abs(x_grid[0] - result_point[0]))
46 | y_idx = np.argmin(np.abs(y_grid[:, 0] - result_point[1]))
47 | z_values[y_idx, x_idx] += 1
48 | end_time = time.perf_counter()
49 | print(f"Average time to get one random coordinate: {(end_time - start_time) / n:.6f} seconds")
50 |
51 | # Interpolate surface
52 | x_flat, y_flat, z_flat = x_grid.flatten(), y_grid.flatten(), z_values.flatten()
53 | grid_x, grid_y = np.meshgrid(np.linspace(min(x_flat), max(x_flat), 200), np.linspace(min(y_flat), max(y_flat), 200))
54 | # noinspection PyTypeChecker
55 | grid_z = griddata((x_flat, y_flat), z_flat, (grid_x, grid_y), method="linear")
56 |
57 | # Plot
58 | fig = plt.figure()
59 | ax = fig.add_subplot(111, projection='3d')
60 |
61 | elem_x = [point[0] for point in elem + [elem[0]]]
62 | elem_y = [point[1] for point in elem + [elem[0]]]
63 | elem_z = [0] * 5
64 | # Label elem corners
65 | for i, corner in enumerate(elem):
66 | # noinspection PyTypeChecker
67 | ax.text(corner[0], corner[1], 0, ["A", "B", "C", "D"][i], ha='right', va='bottom')
68 |
69 | ax.plot(elem_x, elem_y, elem_z, marker='o', label='Rectangle')
70 | ax.plot_surface(grid_x, grid_y, grid_z, cmap='viridis', alpha=0.8, label=f'Distribution')
71 |
72 | # Set equal scaling for x and y axes
73 | ax.set_box_aspect([1, 1, 1])
74 | ax.legend()
75 | ax.grid(True)
76 |
77 | plt.show()
78 |
--------------------------------------------------------------------------------
/dev/human_like_path.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from selenium_driverless.scripts.geometry import gen_combined_path, pos_at_time, bias_0_dot_5
4 |
5 |
6 | def visualize_paths(paths_list, points, transparency=0.5):
7 | plt.figure(figsize=(8, 6))
8 |
9 | for path_points in paths_list:
10 | x_path, y_path = zip(*path_points)
11 | plt.plot(x_path, y_path, color='blue', linewidth=1, alpha=transparency) # Set color and alpha for transparency
12 |
13 | plt.plot(*zip(*points), 'go')
14 | plt.show(block=True)
15 |
16 |
17 | def demo(points, n_paths=30):
18 | paths_list = []
19 |
20 | for _ in range(n_paths):
21 | full_pixel_path = gen_combined_path(points, n_points_soft=5, smooth_soft=10,
22 | n_points_distort=100, smooth_distort=0.4)
23 | paths_list.append(full_pixel_path)
24 |
25 | visualize_paths(paths_list, click_points)
26 |
27 |
28 | def visualize_events(_path, points, total_time, freq=60, accel=3, _mid_time=0.5):
29 | time_interval = 1 / freq
30 | plt.figure(figsize=(8, 6))
31 |
32 | x_path, y_path = zip(*_path)
33 | x_path = np.array(x_path)
34 | y_path = np.array(y_path)
35 |
36 | points_x, points_y = zip(*points)
37 | plt.plot(points_x, points_y, 'go', markersize=8, label='Target Points')
38 | plt.plot(x_path, y_path, color='blue', linewidth=1)
39 |
40 | for t in np.arange(0, total_time + time_interval, time_interval):
41 | coordinates = pos_at_time(_path, total_time, t, accel=accel, mid_time=mid_time)
42 |
43 | plt.plot(coordinates[0], coordinates[1], 'ro', markersize=3)
44 |
45 | plt.title(f"Mousemove Events at {freq} Hz and {total_time} s total time")
46 | plt.xlim(min(x_path) - 20, max(x_path) + 20)
47 | plt.ylim(min(y_path) - 20, max(y_path) + 20)
48 | plt.legend()
49 | plt.show(block=True)
50 |
51 |
52 | click_points = [(10, 100),
53 | (150, 300),
54 | (200, 800)]
55 |
56 | demo(click_points)
57 |
58 | path = gen_combined_path(click_points, n_points_soft=5, smooth_soft=10, n_points_distort=100, smooth_distort=0.4)
59 |
60 | mid_time = bias_0_dot_5(0.5, max_offset=0.3)
61 | print(mid_time)
62 | visualize_events(path, click_points, 1, accel=3, _mid_time=mid_time)
63 |
--------------------------------------------------------------------------------
/dev/show_mousemove.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless import webdriver
2 | from selenium_driverless.utils.utils import read
3 | from selenium_driverless.types.by import By
4 | import asyncio
5 | import aiodebug.log_slow_callbacks
6 |
7 | aiodebug.log_slow_callbacks.enable(0.05)
8 |
9 |
10 | async def main():
11 | options = webdriver.ChromeOptions()
12 | async with webdriver.Chrome(options=options) as driver:
13 | await driver.get("about:blank")
14 | await driver.execute_script(script=await read("/files/js/show_mousemove.js", sel_root=True))
15 | elem = await driver.find_element(By.ID, "clear")
16 | pointer = driver.current_pointer
17 |
18 | move_kwargs = {"total_time": 0.7, "accel": 2, "smooth_soft": 20}
19 | await driver.current_target.activate()
20 |
21 | for _ in range(50):
22 | await pointer.click(100, 500, move_kwargs=move_kwargs, move_to=True)
23 | await asyncio.sleep(0)
24 | await pointer.click(500, 50, move_kwargs=move_kwargs, move_to=True)
25 | await asyncio.sleep(0)
26 | input("Press ENTER to exit")
27 |
28 |
29 | asyncio.run(main())
30 |
--------------------------------------------------------------------------------
/dev/target_interception.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from selenium_driverless import webdriver
3 | import sys
4 | import aiodebug.log_slow_callbacks
5 |
6 | aiodebug.log_slow_callbacks.enable(0.05)
7 |
8 | global driver
9 |
10 | handler = (lambda e: print(f'Exception in event-handler:\n{e.__class__.__module__}.{e.__class__.__name__}: {e}',
11 | file=sys.stderr))
12 | sys.modules["selenium_driverless"].EXC_HANDLER = handler
13 | sys.modules["cdp_socket"].EXC_HANDLER = handler
14 |
15 |
16 | async def attached_callback(data):
17 | global driver
18 | target = await driver.get_target(data["targetInfo"]["targetId"])
19 | print(data["targetInfo"]["url"])
20 | if data['waitingForDebugger']:
21 | await target.execute_cdp_cmd("Runtime.runIfWaitingForDebugger", timeout=2)
22 | raise Exception("testException")
23 |
24 |
25 | async def main():
26 | global driver
27 | options = webdriver.ChromeOptions()
28 | async with webdriver.Chrome(options=options) as driver:
29 | await driver.base_target.execute_cdp_cmd("Target.setDiscoverTargets", {"discover": True})
30 | await driver.base_target.execute_cdp_cmd("Target.setAutoAttach",
31 | {"autoAttach": True, "waitForDebuggerOnStart": True, "flatten": True})
32 | await driver.base_target.add_cdp_listener("Target.attachedToTarget", attached_callback)
33 | await driver.base_target.add_cdp_listener("Target.targetDestroyed", print)
34 | url = "https://abrahamjuliot.github.io/creepjs/tests/workers.html"
35 | await driver.get(url)
36 | await driver.switch_to.new_window(url=url)
37 | a = True
38 | while a:
39 | await asyncio.sleep(2)
40 |
41 |
42 | asyncio.run(main())
43 |
--------------------------------------------------------------------------------
/dev/viewport_intersection.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from selenium_driverless.scripts.geometry import overlap
4 |
5 |
6 | def rectangle_corners(center: np.ndarray, width: float, height: float, angle: float) -> np.ndarray:
7 | """Calculate the corners of a rectangle given center, width, height, and rotation angle."""
8 | angle_rad: float = np.radians(angle)
9 | cos_angle: float = np.cos(angle_rad)
10 | sin_angle: float = np.sin(angle_rad)
11 |
12 | # Half dimensions
13 | w, h = width / 2, height / 2
14 |
15 | # Define the rectangle's corners in local coordinates
16 | corners: np.ndarray = np.array([
17 | [-w, -h],
18 | [w, -h],
19 | [w, h],
20 | [-w, h]
21 | ])
22 |
23 | # Rotate and translate corners
24 | rotation_matrix: np.ndarray = np.array([[cos_angle, -sin_angle],
25 | [sin_angle, cos_angle]])
26 |
27 | return np.dot(corners, rotation_matrix) + center
28 |
29 |
30 | def plot_rect_and_intersect(rect1: np.ndarray, rect2: np.ndarray, intersection: np.ndarray, title: str,
31 | percentage_overlap: float) -> None:
32 | """Plot the rectangles and their intersection."""
33 | plt.figure(figsize=(8, 8))
34 | plt.plot(*rect1.T, label='Rectangle 1', color='blue')
35 | plt.fill(*rect1.T, alpha=0.5, color='blue')
36 | plt.plot(*rect2.T, label='Rectangle 2', color='red')
37 | plt.fill(*rect2.T, alpha=0.5, color='red')
38 |
39 | if intersection.size > 0:
40 | plt.plot(*intersection.T, label='Intersection', color='green')
41 | plt.fill(*intersection.T, alpha=0.5, color='green')
42 |
43 | plt.xlim(-10, 10)
44 | plt.ylim(-10, 10)
45 | plt.axhline(0, color='black', linewidth=0.5, ls='--')
46 | plt.axvline(0, color='black', linewidth=0.5, ls='--')
47 | plt.grid()
48 | plt.gca().set_aspect('equal', adjustable='box')
49 | plt.legend()
50 | plt.title(f'{title} (Overlap: {percentage_overlap:.2f}%)')
51 | plt.show()
52 |
53 |
54 | def demo():
55 | test_cases = [
56 | # Full Inclusion
57 | {
58 | "rect1": (np.array([0, 0]), 8, 4, 0), # center, width, height, angle
59 | "rect2": (np.array([0, 0]), 4, 2, 0),
60 | "title": "Full Inclusion"
61 | },
62 | # Partial Overlap
63 | {
64 | "rect1": (np.array([2, 2]), 6, 4, 30),
65 | "rect2": (np.array([-1, 1]), 4, 6, -45),
66 | "title": "Partial Overlap"
67 | },
68 | # No Intersection
69 | {
70 | "rect1": (np.array([-5, -5]), 2, 1, 0),
71 | "rect2": (np.array([5, 5]), 2, 1, 0),
72 | "title": "No Intersection"
73 | },
74 | # One Rectangle Inside Another
75 | {
76 | "rect1": (np.array([-1, -1]), 4, 4, 0),
77 | "rect2": (np.array([0, 0]), 2, 2, 0),
78 | "title": "One Inside Another"
79 | },
80 | # Complex Overlap with Different Rotations
81 | {
82 | "rect1": (np.array([2, 0]), 9, 3, 45),
83 | "rect2": (np.array([0, 2]), 3, 5, -30),
84 | "title": "Complex Overlap"
85 | },
86 | # One Corner Outside
87 | {
88 | "rect1": (np.array([0, 0]), 4, 4, 0),
89 | "rect2": (np.array([3, 3]), 2, 2, 0),
90 | "title": "One Corner Outside"
91 | },
92 | ]
93 |
94 | for case in test_cases:
95 | center1, width1, height1, angle1 = case["rect1"]
96 | rect1 = rectangle_corners(center1, width1, height1, angle1)
97 |
98 | center2, width2, height2, angle2 = case["rect2"]
99 | rect2 = rectangle_corners(center2, width2, height2, angle2)
100 |
101 | # Calculate percentage overlap and plot
102 | percentage_overlap, intersection_polygon = overlap(rect1, rect2)
103 | plot_rect_and_intersect(rect1, rect2, intersection_polygon, case["title"], percentage_overlap)
104 |
105 |
106 | if __name__ == "__main__":
107 | demo()
108 |
--------------------------------------------------------------------------------
/docs/.buildinfo:
--------------------------------------------------------------------------------
1 | # Sphinx build info version 1
2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3 | config: a84f081f1b5c55542db01694aa110d26
4 | tags: 645f666f9bcd5a90fca523b33c5a78b7
5 |
--------------------------------------------------------------------------------
/docs/.doctrees/api/By.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/By.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/Chrome.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/Chrome.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/ChromeOptions.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/ChromeOptions.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/Context.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/Context.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/Input.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/Input.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/RequestInterception.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/RequestInterception.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/Target.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/Target.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/api/WebELement.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/api/WebELement.doctree
--------------------------------------------------------------------------------
/docs/.doctrees/environment.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/environment.pickle
--------------------------------------------------------------------------------
/docs/.doctrees/index.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.doctrees/index.doctree
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/_modules/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Overview: module code — Selenium-Driverless 1.9.4 documentation
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
57 |
58 |
59 |
60 |
61 | Selenium-Driverless
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Overview: module code
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
All modules for which code is available
79 |
91 |
92 |
93 |
94 |
108 |
109 |
110 |
111 |
112 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/docs/_modules/selenium_driverless/types/by.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | selenium_driverless.types.by — Selenium-Driverless 1.9.4 documentation
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
57 |
58 |
59 |
60 |
61 | Selenium-Driverless
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Module code
70 | selenium_driverless.types.by
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
Source code for selenium_driverless.types.by
80 | # Licensed to the Software Freedom Conservancy (SFC) under one
81 | # or more contributor license agreements. See the NOTICE file
82 | # distributed with this work for additional information
83 | # regarding copyright ownership. The SFC licenses this file
84 | # to you under the Apache License, Version 2.0 (the
85 | # "License"); you may not use this file except in compliance
86 | # with the License. You may obtain a copy of the License at
87 | #
88 | # http://www.apache.org/licenses/LICENSE-2.0
89 | #
90 | # Unless required by applicable law or agreed to in writing,
91 | # software distributed under the License is distributed on an
92 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
93 | # KIND, either express or implied. See the License for the
94 | # specific language governing permissions and limitations
95 | # under the License.
96 | #
97 | # edited by github/kaliiiiiiiiii
98 | # all modifications are licensed under the license provided at LICENSE.md
99 |
100 |
101 | [docs] class By :
102 |
"""Set of supported locator strategies."""
103 |
ID = "id"
104 |
""""""
105 |
NAME = "name"
106 |
""""""
107 |
XPATH = "xpath"
108 |
""""""
109 |
TAG_NAME = "tag name"
110 |
""""""
111 |
CLASS_NAME = "class name"
112 |
""""""
113 |
CSS_SELECTOR = "css selector"
114 |
""""""
115 |
CSS = "css selector"
116 |
"""alias to By.CSS_SELECTOR"""
117 |
118 |
119 |
120 |
121 |
135 |
136 |
137 |
138 |
139 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/docs/_modules/selenium_driverless/types/by/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | selenium_driverless.types.by — Selenium-Driverless 1.9.3.1 documentation
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
57 |
58 |
59 |
60 |
61 | Selenium-Driverless
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Module code
70 | selenium_driverless.types.by
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
Source code for selenium_driverless.types.by
80 | # Licensed to the Software Freedom Conservancy (SFC) under one
81 | # or more contributor license agreements. See the NOTICE file
82 | # distributed with this work for additional information
83 | # regarding copyright ownership. The SFC licenses this file
84 | # to you under the Apache License, Version 2.0 (the
85 | # "License"); you may not use this file except in compliance
86 | # with the License. You may obtain a copy of the License at
87 | #
88 | # http://www.apache.org/licenses/LICENSE-2.0
89 | #
90 | # Unless required by applicable law or agreed to in writing,
91 | # software distributed under the License is distributed on an
92 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
93 | # KIND, either express or implied. See the License for the
94 | # specific language governing permissions and limitations
95 | # under the License.
96 | #
97 | # edited by github/kaliiiiiiiiii
98 | # all modifications are licensed under the license provided at LICENSE.md
99 |
100 |
101 |
102 |
[docs]
103 |
class By :
104 |
"""Set of supported locator strategies."""
105 |
ID = "id"
106 |
""""""
107 |
NAME = "name"
108 |
""""""
109 |
XPATH = "xpath"
110 |
""""""
111 |
TAG_NAME = "tag name"
112 |
""""""
113 |
CLASS_NAME = "class name"
114 |
""""""
115 |
CSS_SELECTOR = "css selector"
116 |
""""""
117 |
CSS = "css selector"
118 |
"""alias to By.CSS_SELECTOR"""
119 |
120 |
121 |
122 |
123 |
124 |
138 |
139 |
140 |
141 |
142 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/docs/_sources/api/By.rst.txt:
--------------------------------------------------------------------------------
1 | By Element Locator
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.by.By
6 | :members: ID, NAME, XPATH, TAG_NAME, CLASS_NAME, CSS_SELECTOR, CSS
--------------------------------------------------------------------------------
/docs/_sources/api/Chrome.rst.txt:
--------------------------------------------------------------------------------
1 | webdriver.Chrome
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.webdriver.Chrome
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.scripts.switch_to.SwitchTo
9 | :members:
--------------------------------------------------------------------------------
/docs/_sources/api/ChromeOptions.rst.txt:
--------------------------------------------------------------------------------
1 | ChromeOptions
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.options.Options
6 | :members:
--------------------------------------------------------------------------------
/docs/_sources/api/Context.rst.txt:
--------------------------------------------------------------------------------
1 | Context
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.context.Context
6 | :members:
--------------------------------------------------------------------------------
/docs/_sources/api/Input.rst.txt:
--------------------------------------------------------------------------------
1 | Input
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.input.pointer.Pointer
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.input.pointer.BasePointer
9 | :members:
10 |
11 | .. autoclass:: selenium_driverless.input.pointer.PointerEvent
12 | :members:
13 |
14 | .. autoclass:: selenium_driverless.input.pointer.Modifiers
15 | :members:
16 |
17 | .. autoclass:: selenium_driverless.input.pointer.PointerType
18 | :members:
19 |
20 | .. autoclass:: selenium_driverless.input.pointer.MouseButton
21 | :members:
22 |
23 | .. autoclass:: selenium_driverless.input.pointer.Buttons
24 | :members:
25 |
26 | .. autoclass:: selenium_driverless.input.pointer.EventType
27 | :members:
28 |
29 | Select Element
30 | ~~~~~~~~~~~~~~~
31 |
32 | .. autofunction:: selenium_driverless.input.utils.select
--------------------------------------------------------------------------------
/docs/_sources/api/RequestInterception.rst.txt:
--------------------------------------------------------------------------------
1 | Request-Interception
2 | ====================
3 |
4 | Example Script
5 | ~~~~~~~~~~~~~~
6 |
7 | .. literalinclude:: files/request_interception.py
8 | :language: Python
9 |
10 | API
11 | ~~~
12 |
13 | .. autoclass:: selenium_driverless.scripts.network_interceptor.NetworkInterceptor
14 | :members:
15 | :special-members: __init__, __aiter__
16 |
17 | .. autoclass:: selenium_driverless.scripts.network_interceptor.InterceptedRequest
18 | :members:
19 |
20 | .. autoclass:: selenium_driverless.scripts.network_interceptor.InterceptedAuth
21 | :members:
22 |
23 | .. autoclass:: selenium_driverless.scripts.network_interceptor.AuthChallenge
24 | :members:
25 |
26 | .. autoclass:: selenium_driverless.scripts.network_interceptor.Request
27 | :members:
28 |
29 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestStages
30 | :members:
31 |
32 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestPattern
33 | :members:
34 |
35 | .. autoclass:: selenium_driverless.scripts.network_interceptor.AuthAlreadyHandledException
36 | :members:
37 |
38 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestDoneException
39 | :members:
--------------------------------------------------------------------------------
/docs/_sources/api/Target.rst.txt:
--------------------------------------------------------------------------------
1 | Target
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.target.Target
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.types.target.TargetInfo
9 | :members:
10 |
11 | .. autoclass:: selenium_driverless.types.base_target.BaseTarget
12 | :members:
--------------------------------------------------------------------------------
/docs/_sources/api/WebELement.rst.txt:
--------------------------------------------------------------------------------
1 | WebElement
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.webelement.WebElement
6 | :members:
--------------------------------------------------------------------------------
/docs/_sources/index.rst.txt:
--------------------------------------------------------------------------------
1 | Documentation of Driverless
2 | ===========================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents:
7 |
8 | .. note::
9 | this is not complete yet at all:)
10 | some methods aren't documented yet properly
11 |
12 | Installation
13 | ==================
14 | .. code-block:: Shell
15 |
16 | python -m pip install --upgrade selenium-driverless
17 |
18 | Usage
19 | ==================
20 | .. code-block:: Python
21 |
22 | from selenium_driverless import webdriver
23 | from selenium_driverless.types.by import By
24 | import asyncio
25 |
26 |
27 | async def main():
28 | options = webdriver.ChromeOptions()
29 | async with webdriver.Chrome(options=options) as driver:
30 | await driver.get('http://nowsecure.nl#relax', wait_load=True)
31 | await driver.sleep(0.5)
32 | await driver.wait_for_cdp("Page.domContentEventFired", timeout=15)
33 |
34 | # wait 10s for elem to exist
35 | elem = await driver.find_element(By.XPATH, '/html/body/div[2]/div/main/p[2]/a', timeout=10)
36 | await elem.click(move_to=True)
37 |
38 | alert = await driver.switch_to.alert
39 | print(alert.text)
40 | await alert.accept()
41 |
42 | print(await driver.title)
43 |
44 |
45 | asyncio.run(main())
46 |
47 |
48 | API
49 | --------
50 |
51 | .. toctree::
52 | :glob:
53 | :maxdepth: 2
54 |
55 | api/*
56 |
57 | Source
58 | --------
59 |
60 | see `github.com/kaliiiiiiiiii/Selenium-Driverless `_
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/_static/_sphinx_javascript_frameworks_compat.js:
--------------------------------------------------------------------------------
1 | /* Compatability shim for jQuery and underscores.js.
2 | *
3 | * Copyright Sphinx contributors
4 | * Released under the two clause BSD licence
5 | */
6 |
7 | /**
8 | * small helper function to urldecode strings
9 | *
10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL
11 | */
12 | jQuery.urldecode = function(x) {
13 | if (!x) {
14 | return x
15 | }
16 | return decodeURIComponent(x.replace(/\+/g, ' '));
17 | };
18 |
19 | /**
20 | * small helper function to urlencode strings
21 | */
22 | jQuery.urlencode = encodeURIComponent;
23 |
24 | /**
25 | * This function returns the parsed url parameters of the
26 | * current request. Multiple values per key are supported,
27 | * it will always return arrays of strings for the value parts.
28 | */
29 | jQuery.getQueryParameters = function(s) {
30 | if (typeof s === 'undefined')
31 | s = document.location.search;
32 | var parts = s.substr(s.indexOf('?') + 1).split('&');
33 | var result = {};
34 | for (var i = 0; i < parts.length; i++) {
35 | var tmp = parts[i].split('=', 2);
36 | var key = jQuery.urldecode(tmp[0]);
37 | var value = jQuery.urldecode(tmp[1]);
38 | if (key in result)
39 | result[key].push(value);
40 | else
41 | result[key] = [value];
42 | }
43 | return result;
44 | };
45 |
46 | /**
47 | * highlight a given string on a jquery object by wrapping it in
48 | * span elements with the given class name.
49 | */
50 | jQuery.fn.highlightText = function(text, className) {
51 | function highlight(node, addItems) {
52 | if (node.nodeType === 3) {
53 | var val = node.nodeValue;
54 | var pos = val.toLowerCase().indexOf(text);
55 | if (pos >= 0 &&
56 | !jQuery(node.parentNode).hasClass(className) &&
57 | !jQuery(node.parentNode).hasClass("nohighlight")) {
58 | var span;
59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg");
60 | if (isInSVG) {
61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
62 | } else {
63 | span = document.createElement("span");
64 | span.className = className;
65 | }
66 | span.appendChild(document.createTextNode(val.substr(pos, text.length)));
67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore(
68 | document.createTextNode(val.substr(pos + text.length)),
69 | node.nextSibling));
70 | node.nodeValue = val.substr(0, pos);
71 | if (isInSVG) {
72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
73 | var bbox = node.parentElement.getBBox();
74 | rect.x.baseVal.value = bbox.x;
75 | rect.y.baseVal.value = bbox.y;
76 | rect.width.baseVal.value = bbox.width;
77 | rect.height.baseVal.value = bbox.height;
78 | rect.setAttribute('class', className);
79 | addItems.push({
80 | "parent": node.parentNode,
81 | "target": rect});
82 | }
83 | }
84 | }
85 | else if (!jQuery(node).is("button, select, textarea")) {
86 | jQuery.each(node.childNodes, function() {
87 | highlight(this, addItems);
88 | });
89 | }
90 | }
91 | var addItems = [];
92 | var result = this.each(function() {
93 | highlight(this, addItems);
94 | });
95 | for (var i = 0; i < addItems.length; ++i) {
96 | jQuery(addItems[i].parent).before(addItems[i].target);
97 | }
98 | return result;
99 | };
100 |
101 | /*
102 | * backward compatibility for jQuery.browser
103 | * This will be supported until firefox bug is fixed.
104 | */
105 | if (!jQuery.browser) {
106 | jQuery.uaMatch = function(ua) {
107 | ua = ua.toLowerCase();
108 |
109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
110 | /(webkit)[ \/]([\w.]+)/.exec(ua) ||
111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
112 | /(msie) ([\w.]+)/.exec(ua) ||
113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
114 | [];
115 |
116 | return {
117 | browser: match[ 1 ] || "",
118 | version: match[ 2 ] || "0"
119 | };
120 | };
121 | jQuery.browser = {};
122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
123 | }
124 |
--------------------------------------------------------------------------------
/docs/_static/css/badge_only.css:
--------------------------------------------------------------------------------
1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
--------------------------------------------------------------------------------
/docs/_static/css/fonts/Roboto-Slab-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/Roboto-Slab-Bold.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/Roboto-Slab-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/Roboto-Slab-Bold.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/Roboto-Slab-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/Roboto-Slab-Regular.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/Roboto-Slab-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/Roboto-Slab-Regular.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/docs/_static/css/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/docs/_static/css/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-bold-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-bold-italic.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-bold-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-bold-italic.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-bold.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-bold.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-normal-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-normal-italic.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-normal-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-normal-italic.woff2
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-normal.woff
--------------------------------------------------------------------------------
/docs/_static/css/fonts/lato-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/css/fonts/lato-normal.woff2
--------------------------------------------------------------------------------
/docs/_static/doctools.js:
--------------------------------------------------------------------------------
1 | /*
2 | * doctools.js
3 | * ~~~~~~~~~~~
4 | *
5 | * Base JavaScript utilities for all Sphinx HTML documentation.
6 | *
7 | * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 | "use strict";
12 |
13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
14 | "TEXTAREA",
15 | "INPUT",
16 | "SELECT",
17 | "BUTTON",
18 | ]);
19 |
20 | const _ready = (callback) => {
21 | if (document.readyState !== "loading") {
22 | callback();
23 | } else {
24 | document.addEventListener("DOMContentLoaded", callback);
25 | }
26 | };
27 |
28 | /**
29 | * Small JavaScript module for the documentation.
30 | */
31 | const Documentation = {
32 | init: () => {
33 | Documentation.initDomainIndexTable();
34 | Documentation.initOnKeyListeners();
35 | },
36 |
37 | /**
38 | * i18n support
39 | */
40 | TRANSLATIONS: {},
41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
42 | LOCALE: "unknown",
43 |
44 | // gettext and ngettext don't access this so that the functions
45 | // can safely bound to a different name (_ = Documentation.gettext)
46 | gettext: (string) => {
47 | const translated = Documentation.TRANSLATIONS[string];
48 | switch (typeof translated) {
49 | case "undefined":
50 | return string; // no translation
51 | case "string":
52 | return translated; // translation exists
53 | default:
54 | return translated[0]; // (singular, plural) translation tuple exists
55 | }
56 | },
57 |
58 | ngettext: (singular, plural, n) => {
59 | const translated = Documentation.TRANSLATIONS[singular];
60 | if (typeof translated !== "undefined")
61 | return translated[Documentation.PLURAL_EXPR(n)];
62 | return n === 1 ? singular : plural;
63 | },
64 |
65 | addTranslations: (catalog) => {
66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages);
67 | Documentation.PLURAL_EXPR = new Function(
68 | "n",
69 | `return (${catalog.plural_expr})`
70 | );
71 | Documentation.LOCALE = catalog.locale;
72 | },
73 |
74 | /**
75 | * helper function to focus on search bar
76 | */
77 | focusSearchBar: () => {
78 | document.querySelectorAll("input[name=q]")[0]?.focus();
79 | },
80 |
81 | /**
82 | * Initialise the domain index toggle buttons
83 | */
84 | initDomainIndexTable: () => {
85 | const toggler = (el) => {
86 | const idNumber = el.id.substr(7);
87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
88 | if (el.src.substr(-9) === "minus.png") {
89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
90 | toggledRows.forEach((el) => (el.style.display = "none"));
91 | } else {
92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
93 | toggledRows.forEach((el) => (el.style.display = ""));
94 | }
95 | };
96 |
97 | const togglerElements = document.querySelectorAll("img.toggler");
98 | togglerElements.forEach((el) =>
99 | el.addEventListener("click", (event) => toggler(event.currentTarget))
100 | );
101 | togglerElements.forEach((el) => (el.style.display = ""));
102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
103 | },
104 |
105 | initOnKeyListeners: () => {
106 | // only install a listener if it is really needed
107 | if (
108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
110 | )
111 | return;
112 |
113 | document.addEventListener("keydown", (event) => {
114 | // bail for input elements
115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
116 | // bail with special keys
117 | if (event.altKey || event.ctrlKey || event.metaKey) return;
118 |
119 | if (!event.shiftKey) {
120 | switch (event.key) {
121 | case "ArrowLeft":
122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
123 |
124 | const prevLink = document.querySelector('link[rel="prev"]');
125 | if (prevLink && prevLink.href) {
126 | window.location.href = prevLink.href;
127 | event.preventDefault();
128 | }
129 | break;
130 | case "ArrowRight":
131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
132 |
133 | const nextLink = document.querySelector('link[rel="next"]');
134 | if (nextLink && nextLink.href) {
135 | window.location.href = nextLink.href;
136 | event.preventDefault();
137 | }
138 | break;
139 | }
140 | }
141 |
142 | // some keyboard layouts may need Shift to get /
143 | switch (event.key) {
144 | case "/":
145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
146 | Documentation.focusSearchBar();
147 | event.preventDefault();
148 | }
149 | });
150 | },
151 | };
152 |
153 | // quick alias for translations
154 | const _ = Documentation.gettext;
155 |
156 | _ready(Documentation.init);
157 |
--------------------------------------------------------------------------------
/docs/_static/documentation_options.js:
--------------------------------------------------------------------------------
1 | var DOCUMENTATION_OPTIONS = {
2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
3 | VERSION: '1.9.4',
4 | LANGUAGE: 'en',
5 | COLLAPSE_INDEX: false,
6 | BUILDER: 'html',
7 | FILE_SUFFIX: '.html',
8 | LINK_SUFFIX: '.html',
9 | HAS_SOURCE: true,
10 | SOURCELINK_SUFFIX: '.txt',
11 | NAVIGATION_WITH_KEYS: false,
12 | SHOW_SEARCH_SUMMARY: true,
13 | ENABLE_SEARCH_SHORTCUTS: true,
14 | };
--------------------------------------------------------------------------------
/docs/_static/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/file.png
--------------------------------------------------------------------------------
/docs/_static/js/badge_only.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}});
--------------------------------------------------------------------------------
/docs/_static/js/html5shiv-printshiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML=" ",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document);
--------------------------------------------------------------------------------
/docs/_static/js/html5shiv.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
3 | */
4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML=" ",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document);
--------------------------------------------------------------------------------
/docs/_static/js/theme.js:
--------------------------------------------------------------------------------
1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap(""),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(' '),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0
63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
65 | var s_v = "^(" + C + ")?" + v; // vowel in stem
66 |
67 | this.stemWord = function (w) {
68 | var stem;
69 | var suffix;
70 | var firstch;
71 | var origword = w;
72 |
73 | if (w.length < 3)
74 | return w;
75 |
76 | var re;
77 | var re2;
78 | var re3;
79 | var re4;
80 |
81 | firstch = w.substr(0,1);
82 | if (firstch == "y")
83 | w = firstch.toUpperCase() + w.substr(1);
84 |
85 | // Step 1a
86 | re = /^(.+?)(ss|i)es$/;
87 | re2 = /^(.+?)([^s])s$/;
88 |
89 | if (re.test(w))
90 | w = w.replace(re,"$1$2");
91 | else if (re2.test(w))
92 | w = w.replace(re2,"$1$2");
93 |
94 | // Step 1b
95 | re = /^(.+?)eed$/;
96 | re2 = /^(.+?)(ed|ing)$/;
97 | if (re.test(w)) {
98 | var fp = re.exec(w);
99 | re = new RegExp(mgr0);
100 | if (re.test(fp[1])) {
101 | re = /.$/;
102 | w = w.replace(re,"");
103 | }
104 | }
105 | else if (re2.test(w)) {
106 | var fp = re2.exec(w);
107 | stem = fp[1];
108 | re2 = new RegExp(s_v);
109 | if (re2.test(stem)) {
110 | w = stem;
111 | re2 = /(at|bl|iz)$/;
112 | re3 = new RegExp("([^aeiouylsz])\\1$");
113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
114 | if (re2.test(w))
115 | w = w + "e";
116 | else if (re3.test(w)) {
117 | re = /.$/;
118 | w = w.replace(re,"");
119 | }
120 | else if (re4.test(w))
121 | w = w + "e";
122 | }
123 | }
124 |
125 | // Step 1c
126 | re = /^(.+?)y$/;
127 | if (re.test(w)) {
128 | var fp = re.exec(w);
129 | stem = fp[1];
130 | re = new RegExp(s_v);
131 | if (re.test(stem))
132 | w = stem + "i";
133 | }
134 |
135 | // Step 2
136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
137 | if (re.test(w)) {
138 | var fp = re.exec(w);
139 | stem = fp[1];
140 | suffix = fp[2];
141 | re = new RegExp(mgr0);
142 | if (re.test(stem))
143 | w = stem + step2list[suffix];
144 | }
145 |
146 | // Step 3
147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
148 | if (re.test(w)) {
149 | var fp = re.exec(w);
150 | stem = fp[1];
151 | suffix = fp[2];
152 | re = new RegExp(mgr0);
153 | if (re.test(stem))
154 | w = stem + step3list[suffix];
155 | }
156 |
157 | // Step 4
158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
159 | re2 = /^(.+?)(s|t)(ion)$/;
160 | if (re.test(w)) {
161 | var fp = re.exec(w);
162 | stem = fp[1];
163 | re = new RegExp(mgr1);
164 | if (re.test(stem))
165 | w = stem;
166 | }
167 | else if (re2.test(w)) {
168 | var fp = re2.exec(w);
169 | stem = fp[1] + fp[2];
170 | re2 = new RegExp(mgr1);
171 | if (re2.test(stem))
172 | w = stem;
173 | }
174 |
175 | // Step 5
176 | re = /^(.+?)e$/;
177 | if (re.test(w)) {
178 | var fp = re.exec(w);
179 | stem = fp[1];
180 | re = new RegExp(mgr1);
181 | re2 = new RegExp(meq1);
182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
184 | w = stem;
185 | }
186 | re = /ll$/;
187 | re2 = new RegExp(mgr1);
188 | if (re.test(w) && re2.test(w)) {
189 | re = /.$/;
190 | w = w.replace(re,"");
191 | }
192 |
193 | // and turn initial Y back to y
194 | if (firstch == "y")
195 | w = firstch.toLowerCase() + w.substr(1);
196 | return w;
197 | }
198 | }
199 |
200 |
--------------------------------------------------------------------------------
/docs/_static/minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/minus.png
--------------------------------------------------------------------------------
/docs/_static/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/_static/plus.png
--------------------------------------------------------------------------------
/docs/_static/pygments.css:
--------------------------------------------------------------------------------
1 | pre { line-height: 125%; }
2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
6 | .highlight .hll { background-color: #ffffcc }
7 | .highlight { background: #f8f8f8; }
8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */
9 | .highlight .err { border: 1px solid #FF0000 } /* Error */
10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */
11 | .highlight .o { color: #666666 } /* Operator */
12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */
15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */
19 | .highlight .ge { font-style: italic } /* Generic.Emph */
20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
21 | .highlight .gr { color: #E40000 } /* Generic.Error */
22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
23 | .highlight .gi { color: #008400 } /* Generic.Inserted */
24 | .highlight .go { color: #717171 } /* Generic.Output */
25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
26 | .highlight .gs { font-weight: bold } /* Generic.Strong */
27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */
29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */
33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
34 | .highlight .kt { color: #B00040 } /* Keyword.Type */
35 | .highlight .m { color: #666666 } /* Literal.Number */
36 | .highlight .s { color: #BA2121 } /* Literal.String */
37 | .highlight .na { color: #687822 } /* Name.Attribute */
38 | .highlight .nb { color: #008000 } /* Name.Builtin */
39 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
40 | .highlight .no { color: #880000 } /* Name.Constant */
41 | .highlight .nd { color: #AA22FF } /* Name.Decorator */
42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */
43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
44 | .highlight .nf { color: #0000FF } /* Name.Function */
45 | .highlight .nl { color: #767600 } /* Name.Label */
46 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */
48 | .highlight .nv { color: #19177C } /* Name.Variable */
49 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
51 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */
52 | .highlight .mf { color: #666666 } /* Literal.Number.Float */
53 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */
54 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */
55 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */
56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */
57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */
58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */
59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */
60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */
62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */
64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
65 | .highlight .sx { color: #008000 } /* Literal.String.Other */
66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */
67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */
68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */
69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */
70 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */
71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */
72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */
73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */
74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */
75 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/docs/_static/sphinx_highlight.js:
--------------------------------------------------------------------------------
1 | /* Highlighting utilities for Sphinx HTML documentation. */
2 | "use strict";
3 |
4 | const SPHINX_HIGHLIGHT_ENABLED = true
5 |
6 | /**
7 | * highlight a given string on a node by wrapping it in
8 | * span elements with the given class name.
9 | */
10 | const _highlight = (node, addItems, text, className) => {
11 | if (node.nodeType === Node.TEXT_NODE) {
12 | const val = node.nodeValue;
13 | const parent = node.parentNode;
14 | const pos = val.toLowerCase().indexOf(text);
15 | if (
16 | pos >= 0 &&
17 | !parent.classList.contains(className) &&
18 | !parent.classList.contains("nohighlight")
19 | ) {
20 | let span;
21 |
22 | const closestNode = parent.closest("body, svg, foreignObject");
23 | const isInSVG = closestNode && closestNode.matches("svg");
24 | if (isInSVG) {
25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
26 | } else {
27 | span = document.createElement("span");
28 | span.classList.add(className);
29 | }
30 |
31 | span.appendChild(document.createTextNode(val.substr(pos, text.length)));
32 | parent.insertBefore(
33 | span,
34 | parent.insertBefore(
35 | document.createTextNode(val.substr(pos + text.length)),
36 | node.nextSibling
37 | )
38 | );
39 | node.nodeValue = val.substr(0, pos);
40 |
41 | if (isInSVG) {
42 | const rect = document.createElementNS(
43 | "http://www.w3.org/2000/svg",
44 | "rect"
45 | );
46 | const bbox = parent.getBBox();
47 | rect.x.baseVal.value = bbox.x;
48 | rect.y.baseVal.value = bbox.y;
49 | rect.width.baseVal.value = bbox.width;
50 | rect.height.baseVal.value = bbox.height;
51 | rect.setAttribute("class", className);
52 | addItems.push({ parent: parent, target: rect });
53 | }
54 | }
55 | } else if (node.matches && !node.matches("button, select, textarea")) {
56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
57 | }
58 | };
59 | const _highlightText = (thisNode, text, className) => {
60 | let addItems = [];
61 | _highlight(thisNode, addItems, text, className);
62 | addItems.forEach((obj) =>
63 | obj.parent.insertAdjacentElement("beforebegin", obj.target)
64 | );
65 | };
66 |
67 | /**
68 | * Small JavaScript module for the documentation.
69 | */
70 | const SphinxHighlight = {
71 |
72 | /**
73 | * highlight the search words provided in localstorage in the text
74 | */
75 | highlightSearchWords: () => {
76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
77 |
78 | // get and clear terms from localstorage
79 | const url = new URL(window.location);
80 | const highlight =
81 | localStorage.getItem("sphinx_highlight_terms")
82 | || url.searchParams.get("highlight")
83 | || "";
84 | localStorage.removeItem("sphinx_highlight_terms")
85 | url.searchParams.delete("highlight");
86 | window.history.replaceState({}, "", url);
87 |
88 | // get individual terms from highlight string
89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
90 | if (terms.length === 0) return; // nothing to do
91 |
92 | // There should never be more than one element matching "div.body"
93 | const divBody = document.querySelectorAll("div.body");
94 | const body = divBody.length ? divBody[0] : document.querySelector("body");
95 | window.setTimeout(() => {
96 | terms.forEach((term) => _highlightText(body, term, "highlighted"));
97 | }, 10);
98 |
99 | const searchBox = document.getElementById("searchbox");
100 | if (searchBox === null) return;
101 | searchBox.appendChild(
102 | document
103 | .createRange()
104 | .createContextualFragment(
105 | '' +
106 | '' +
107 | _("Hide Search Matches") +
108 | "
"
109 | )
110 | );
111 | },
112 |
113 | /**
114 | * helper function to hide the search marks again
115 | */
116 | hideSearchWords: () => {
117 | document
118 | .querySelectorAll("#searchbox .highlight-link")
119 | .forEach((el) => el.remove());
120 | document
121 | .querySelectorAll("span.highlighted")
122 | .forEach((el) => el.classList.remove("highlighted"));
123 | localStorage.removeItem("sphinx_highlight_terms")
124 | },
125 |
126 | initEscapeListener: () => {
127 | // only install a listener if it is really needed
128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
129 |
130 | document.addEventListener("keydown", (event) => {
131 | // bail for input elements
132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
133 | // bail with special keys
134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
136 | SphinxHighlight.hideSearchWords();
137 | event.preventDefault();
138 | }
139 | });
140 | },
141 | };
142 |
143 | _ready(SphinxHighlight.highlightSearchWords);
144 | _ready(SphinxHighlight.initEscapeListener);
145 |
--------------------------------------------------------------------------------
/docs/objects.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/docs/objects.inv
--------------------------------------------------------------------------------
/docs/py-modindex/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Python Module Index — Selenium-Driverless 1.8.0.2 documentation
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
59 |
60 |
61 |
62 |
63 | Selenium-Driverless
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Python Module Index
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
Python Module Index
82 |
83 |
86 |
87 |
103 |
104 |
105 |
106 |
107 |
121 |
122 |
123 |
124 |
125 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/docs/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Search — Selenium-Driverless 1.9.4 documentation
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
60 |
61 |
62 |
63 |
64 | Selenium-Driverless
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Search
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Please activate JavaScript to enable the search functionality.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
110 |
111 |
112 |
113 |
114 |
119 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/docs/search/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Search — Selenium-Driverless 1.9.3.1 documentation
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
60 |
61 |
62 |
63 |
64 | Selenium-Driverless
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Search
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Please activate JavaScript to enable the search functionality.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
110 |
111 |
112 |
113 |
114 |
119 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/docs_source/api/By.rst:
--------------------------------------------------------------------------------
1 | By Element Locator
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.by.By
6 | :members: ID, NAME, XPATH, TAG_NAME, CLASS_NAME, CSS_SELECTOR, CSS
--------------------------------------------------------------------------------
/docs_source/api/Chrome.rst:
--------------------------------------------------------------------------------
1 | webdriver.Chrome
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.webdriver.Chrome
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.scripts.switch_to.SwitchTo
9 | :members:
--------------------------------------------------------------------------------
/docs_source/api/ChromeOptions.rst:
--------------------------------------------------------------------------------
1 | ChromeOptions
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.options.Options
6 | :members:
--------------------------------------------------------------------------------
/docs_source/api/Context.rst:
--------------------------------------------------------------------------------
1 | Context
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.context.Context
6 | :members:
--------------------------------------------------------------------------------
/docs_source/api/Input.rst:
--------------------------------------------------------------------------------
1 | Input
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.input.pointer.Pointer
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.input.pointer.BasePointer
9 | :members:
10 |
11 | .. autoclass:: selenium_driverless.input.pointer.PointerEvent
12 | :members:
13 |
14 | .. autoclass:: selenium_driverless.input.pointer.Modifiers
15 | :members:
16 |
17 | .. autoclass:: selenium_driverless.input.pointer.PointerType
18 | :members:
19 |
20 | .. autoclass:: selenium_driverless.input.pointer.MouseButton
21 | :members:
22 |
23 | .. autoclass:: selenium_driverless.input.pointer.Buttons
24 | :members:
25 |
26 | .. autoclass:: selenium_driverless.input.pointer.EventType
27 | :members:
28 |
29 | Select Element
30 | ~~~~~~~~~~~~~~~
31 |
32 | .. autofunction:: selenium_driverless.input.utils.select
--------------------------------------------------------------------------------
/docs_source/api/RequestInterception.rst:
--------------------------------------------------------------------------------
1 | Request-Interception
2 | ====================
3 |
4 | Example Script
5 | ~~~~~~~~~~~~~~
6 |
7 | .. literalinclude:: files/request_interception.py
8 | :language: Python
9 |
10 | API
11 | ~~~
12 |
13 | .. autoclass:: selenium_driverless.scripts.network_interceptor.NetworkInterceptor
14 | :members:
15 | :special-members: __init__, __aiter__
16 |
17 | .. autoclass:: selenium_driverless.scripts.network_interceptor.InterceptedRequest
18 | :members:
19 |
20 | .. autoclass:: selenium_driverless.scripts.network_interceptor.InterceptedAuth
21 | :members:
22 |
23 | .. autoclass:: selenium_driverless.scripts.network_interceptor.AuthChallenge
24 | :members:
25 |
26 | .. autoclass:: selenium_driverless.scripts.network_interceptor.Request
27 | :members:
28 |
29 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestStages
30 | :members:
31 |
32 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestPattern
33 | :members:
34 |
35 | .. autoclass:: selenium_driverless.scripts.network_interceptor.AuthAlreadyHandledException
36 | :members:
37 |
38 | .. autoclass:: selenium_driverless.scripts.network_interceptor.RequestDoneException
39 | :members:
--------------------------------------------------------------------------------
/docs_source/api/Target.rst:
--------------------------------------------------------------------------------
1 | Target
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.target.Target
6 | :members:
7 |
8 | .. autoclass:: selenium_driverless.types.target.TargetInfo
9 | :members:
10 |
11 | .. autoclass:: selenium_driverless.types.base_target.BaseTarget
12 | :members:
--------------------------------------------------------------------------------
/docs_source/api/WebELement.rst:
--------------------------------------------------------------------------------
1 | WebElement
2 | ===============================================
3 |
4 |
5 | .. autoclass:: selenium_driverless.types.webelement.WebElement
6 | :members:
--------------------------------------------------------------------------------
/docs_source/api/files/request_interception.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import json
3 | from selenium_driverless import webdriver
4 | from selenium_driverless.scripts.network_interceptor import NetworkInterceptor, InterceptedRequest, InterceptedAuth, \
5 | RequestPattern, RequestStages
6 |
7 |
8 | async def on_request(data: InterceptedRequest):
9 | if data.request.url == "https://httpbin.org/post":
10 | await data.continue_request(url="https://httpbin.org/basic-auth/user/pass", intercept_response=True)
11 |
12 |
13 | async def main():
14 | async with webdriver.Chrome(max_ws_size=2 ** 30) as driver:
15 |
16 | async with NetworkInterceptor(driver, on_request=on_request, patterns=[RequestPattern.AnyRequest],
17 | intercept_auth=True) as interceptor:
18 |
19 | asyncio.ensure_future(driver.get("https://httpbin.org/post", wait_load=False))
20 | async for data in interceptor:
21 | if data.request.url == "https://httpbin.org/basic-auth/user/pass":
22 | if isinstance(data, InterceptedAuth):
23 | # iteration should take virtually zero time, as that would block other requests
24 | asyncio.ensure_future(data.continue_auth(username="user", password="pass"))
25 | elif data.stage == RequestStages.Response:
26 | print(json.loads(await data.body))
27 | break
28 |
29 |
30 | asyncio.run(main())
31 |
--------------------------------------------------------------------------------
/docs_source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 | import sys
9 |
10 | sys.path.insert(0, r"/")
11 | from selenium_driverless import __version__
12 |
13 | project = 'Selenium-Driverless'
14 | # noinspection PyShadowingBuiltins
15 | copyright = '2024, Aurin Aegerter (aka Steve, kaliiiiiiiiii)'
16 | author = 'Aurin Aegerter (aka Steve, kaliiiiiiiiii)'
17 | release = __version__
18 |
19 | # -- General configuration ---------------------------------------------------
20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
21 |
22 | extensions = [
23 | 'sphinx.ext.duration',
24 | 'sphinx.ext.doctest',
25 | 'sphinx.ext.autodoc',
26 | 'sphinx.ext.autosummary',
27 | "sphinx_autodoc_typehints",
28 | "sphinx.ext.viewcode"
29 | ]
30 |
31 | templates_path = ['_templates']
32 | exclude_patterns = []
33 |
34 | # -- Options for HTML output -------------------------------------------------
35 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
36 |
37 | html_theme = 'sphinx_rtd_theme'
38 | html_static_path = ['_static']
39 |
40 |
41 | # -- autodoc options --
42 |
43 | autodoc_member_order = 'bysource'
44 |
--------------------------------------------------------------------------------
/docs_source/index.rst:
--------------------------------------------------------------------------------
1 | Documentation of Driverless
2 | ===========================
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 | :caption: Contents:
7 |
8 | .. note::
9 | this is not complete yet at all:)
10 | some methods aren't documented yet properly
11 |
12 | Installation
13 | ==================
14 | .. code-block:: Shell
15 |
16 | python -m pip install --upgrade selenium-driverless
17 |
18 | Usage
19 | ==================
20 | .. code-block:: Python
21 |
22 | from selenium_driverless import webdriver
23 | from selenium_driverless.types.by import By
24 | import asyncio
25 |
26 |
27 | async def main():
28 | options = webdriver.ChromeOptions()
29 | async with webdriver.Chrome(options=options) as driver:
30 | await driver.get('http://nowsecure.nl#relax', wait_load=True)
31 | await driver.sleep(0.5)
32 | await driver.wait_for_cdp("Page.domContentEventFired", timeout=15)
33 |
34 | # wait 10s for elem to exist
35 | elem = await driver.find_element(By.XPATH, '/html/body/div[2]/div/main/p[2]/a', timeout=10)
36 | await elem.click(move_to=True)
37 |
38 | alert = await driver.switch_to.alert
39 | print(alert.text)
40 | await alert.accept()
41 |
42 | print(await driver.title)
43 |
44 |
45 | asyncio.run(main())
46 |
47 |
48 | API
49 | --------
50 |
51 | .. toctree::
52 | :glob:
53 | :maxdepth: 2
54 |
55 | api/*
56 |
57 | Source
58 | --------
59 |
60 | see `github.com/kaliiiiiiiiii/Selenium-Driverless `_
61 |
62 |
63 |
--------------------------------------------------------------------------------
/examples/proxy_with_auth.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless import webdriver
2 | import asyncio
3 |
4 |
5 | async def main():
6 | # socks5 with credentials not supported due to https://bugs.chromium.org/p/chromium/issues/detail?id=1309413
7 | proxy = "http://user1:passwrd1@example.proxy.com:5001/"
8 |
9 | options = webdriver.ChromeOptions()
10 | # options.single_proxy = proxy
11 |
12 | async with webdriver.Chrome(options=options) as driver:
13 |
14 | # this will overwrite the proxy for ALL CONTEXTS
15 | await driver.set_single_proxy(proxy)
16 |
17 | await driver.get("https://browserleaks.com/webrtc")
18 | await driver.clear_proxy() # clear proxy
19 | print()
20 |
21 |
22 | asyncio.run(main())
23 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless import webdriver
2 | import asyncio
3 |
4 |
5 | async def main():
6 | options = webdriver.ChromeOptions()
7 | async with webdriver.Chrome(options=options) as driver:
8 | await driver.get('https://abrahamjuliot.github.io/creepjs/', wait_load=True)
9 | print(await driver.title)
10 |
11 |
12 | asyncio.run(main())
13 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=46.4.0", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | selenium
2 | cdp-socket>=1.0.6
3 | numpy
4 | aiofiles~=23.2
5 | matplotlib~=3.8.1
6 | scipy
7 | platformdirs
8 | websockets~=12.0
9 | aiohttp~=3.9.3
10 |
11 | # dev
12 | setuptools~=69.0.3
13 | twine
14 | pytest
15 | sphinx-rtd-theme
16 | sphinx-autodoc-typehints
17 | sphinx
18 | jsondiff
19 | typing_extensions
20 | aiodebug
21 | pytest-asyncio
22 | cdp-patches
23 | pytest-subtests
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | version = attr: selenium_driverless.__version__
3 | license_files = LICENSE.md
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | requirements = ['selenium~=4.6', "cdp-socket>=1.2.5", "numpy", "scipy~=1.7", "aiofiles",
4 | 'platformdirs']
5 |
6 | with open('README.md', 'r', encoding='utf-8') as fh:
7 | long_description = fh.read()
8 |
9 | setuptools.setup(
10 | name='selenium_driverless',
11 | author='Aurin Aegerter',
12 | author_email='aurinliun@gmx.ch',
13 | description='Undetected selenium without chromedriver usage (Non-commercial use only!)',
14 | keywords='Selenium, webautomation',
15 | long_description=long_description,
16 | long_description_content_type='text/markdown',
17 | url='https://github.com/kaliiiiiiiiii/Selenium-Driverless',
18 | project_urls={
19 | 'Documentation': 'https://github.com/kaliiiiiiiiii/Selenium-Driverless',
20 | 'Bug Reports':
21 | 'https://github.com/kaliiiiiiiiii/Selenium-Driverless/issues',
22 | 'Source Code': 'https://github.com/kaliiiiiiiiii/Selenium-Driverless',
23 | 'LICENSE':'https://github.com/kaliiiiiiiiii/Selenium-Driverless/blob/master/LICENSE.md'
24 | },
25 | package_dir={'': 'src'},
26 | packages=setuptools.find_namespace_packages(where='src'),
27 | classifiers=[
28 | # see https://pypi.org/classifiers/
29 | 'Development Status :: 2 - Pre-Alpha',
30 | 'Intended Audience :: Developers',
31 | 'Programming Language :: Python :: 3',
32 | 'Programming Language :: Python :: 3.8',
33 | 'Programming Language :: Python :: 3.9',
34 | 'Programming Language :: Python :: 3.10',
35 | 'Programming Language :: Python :: 3.11',
36 | 'License :: Free for non-commercial use',
37 | 'Natural Language :: English',
38 | 'Operating System :: OS Independent',
39 | 'Topic :: Internet :: Proxy Servers',
40 | 'Topic :: Internet',
41 | 'Topic :: Internet :: WWW/HTTP :: Browsers',
42 |
43 | ],
44 | python_requires='>=3.8',
45 | install_requires=requirements,
46 | include_package_data=True,
47 | extras_require={
48 | 'dev': ['check-manifest'],
49 | # 'test': ['coverage'],
50 | },
51 | license='https://github.com/kaliiiiiiiiii/Selenium-Driverless/blob/master/LICENSE.md'
52 | )
53 |
--------------------------------------------------------------------------------
/src/selenium_driverless/__init__.py:
--------------------------------------------------------------------------------
1 | import traceback
2 |
3 | EXC_HANDLER = (lambda e: traceback.print_exc())
4 | __version__ = "1.9.4"
5 |
--------------------------------------------------------------------------------
/src/selenium_driverless/files/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/src/selenium_driverless/files/__init__.py
--------------------------------------------------------------------------------
/src/selenium_driverless/files/mv3_extension/driverless_background_mv3_243ffdd55e32a012b4f253b2879af978.js:
--------------------------------------------------------------------------------
1 | // Forcing service worker to stay alive by sending a "ping" to a port where noone is listening
2 | // Essentially it prevents SW to fall asleep after the first 30 secs of work.
3 |
4 | const INTERNAL_STAYALIVE_PORT = "Whatever_Port_Name_You_Want"
5 | var alivePort = null;
6 | StayAlive();
7 |
8 | async function StayAlive() {
9 | var lastCall = Date.now();
10 | var wakeup = setInterval( () => {
11 |
12 | const now = Date.now();
13 | const age = now - lastCall;
14 |
15 | if (alivePort == null) {
16 | alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})
17 |
18 | alivePort.onDisconnect.addListener( (p) => {
19 | alivePort = null;
20 | if(chrome.runtime.lastError){}
21 | });
22 | }
23 |
24 | if (alivePort) {
25 | alivePort.postMessage({content: "ping"});
26 | }
27 | }, 25000);
28 | }
--------------------------------------------------------------------------------
/src/selenium_driverless/files/mv3_extension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "selenium-driverless-util-mv3",
4 | "version": "1.0",
5 | "description": "util extension for selenium-driverless",
6 | "author": "aurin.aegerter@stud.gymthun.ch",
7 | "background": {
8 | "service_worker": "driverless_background_mv3_243ffdd55e32a012b4f253b2879af978.js", "type": "module"
9 | },
10 | "incognito": "spanning",
11 | "permissions": [
12 | "activeTab",
13 | "alarms",
14 | "background",
15 | "bookmarks",
16 | "browsingData",
17 | "certificateProvider",
18 | "clipboardRead",
19 | "clipboardWrite",
20 | "contentSettings",
21 | "contextMenus",
22 | "cookies",
23 | "debugger",
24 | "declarativeContent",
25 | "declarativeNetRequest",
26 | "declarativeNetRequestWithHostAccess",
27 | "declarativeNetRequestFeedback",
28 | "declarativeWebRequest",
29 | "desktopCapture",
30 | "documentScan",
31 | "downloads",
32 | "enterprise.deviceAttributes",
33 | "enterprise.hardwarePlatform",
34 | "enterprise.networkingAttributes",
35 | "enterprise.platformKeys",
36 | "experimental",
37 | "fileBrowserHandler",
38 | "fileSystemProvider",
39 | "fontSettings",
40 | "gcm",
41 | "geolocation",
42 | "history",
43 | "identity",
44 | "idle",
45 | "loginState",
46 | "management",
47 | "nativeMessaging",
48 | "notifications",
49 | "offscreen",
50 | "pageCapture",
51 | "platformKeys",
52 | "power",
53 | "printerProvider",
54 | "printing",
55 | "printingMetrics",
56 | "privacy",
57 | "processes",
58 | "proxy",
59 | "scripting",
60 | "search",
61 | "sessions",
62 | "storage",
63 | "system.cpu",
64 | "system.display",
65 | "system.memory",
66 | "system.storage",
67 | "tabCapture",
68 | "tabGroups",
69 | "tabs",
70 | "topSites",
71 | "tts",
72 | "ttsEngine",
73 | "unlimitedStorage",
74 | "vpnProvider",
75 | "wallpaper",
76 | "webNavigation",
77 | "webRequest",
78 | "webRequestBlocking",
79 | "webRequestAuthProvider"
80 | ],
81 | "host_permissions": [""]
82 | }
--------------------------------------------------------------------------------
/src/selenium_driverless/input/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/src/selenium_driverless/input/__init__.py
--------------------------------------------------------------------------------
/src/selenium_driverless/input/utils.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless.types.webelement import WebElement
2 | from selenium_driverless.types import JSEvalException
3 | import asyncio
4 | from typing import Type
5 | try:
6 | from cdp_patches.input import AsyncInput
7 | from cdp_patches.input import KeyboardCodes
8 | except ImportError:
9 | # noinspection PyTypeChecker
10 | AsyncInput: Type["AsyncInput"] = "AsyncInput"
11 | KeyboardCodes: Type["KeyboardCodes"] = "KeyboardCodes"
12 |
13 |
14 | async def select(elem: WebElement, value: str = None, text: str = None, async_input: AsyncInput = None,
15 | timeouts: float = 0.01) -> None:
16 | """
17 | Selects an option of a ```` element
18 |
19 | :param elem: ```` element to select an option from
20 | :param value: value for the option to select (can be different from the text!)
21 | :param text: the text of the option to select
22 | :param async_input: an instance of ``cdp_patches.input.AsyncInput``
23 | :param timeouts: timeout in seconds between actions performed to select an option
24 |
25 | .. warning::
26 | this is potentially detectable without the use of `CDP-patches `_ !
27 | """
28 | use_js = async_input is None
29 | if use_js:
30 | await elem.click()
31 | else:
32 | x, y = await elem.mid_location()
33 | await async_input.click("left", x, y)
34 | if value is None and text is None:
35 | raise ValueError("value or text need to be specified")
36 | try:
37 | n, direction = await elem.execute_script("""
38 | let [value, text, use_js] = arguments
39 | var idx = Array.from(obj.options).findIndex(option => option.value === value || option.text === text)
40 | var currIdx = obj.selectedIndex
41 | if (idx === -1){throw ReferenceError("option not found")}
42 | if(typeof value == 'undefined'){
43 | value = obj.options[idx].value
44 | }
45 | if(use_js && obj.options[currIdx].value !== value){
46 | obj.value = value
47 | const evt = new Event("change")
48 | evt.initEvent("change", true, true)
49 | obj.dispatchEvent(evt)
50 | return [0, 1]
51 | }else{
52 | const n = Math.abs(idx - currIdx);
53 | const direction = idx < currIdx ? 1 : -1
54 | return [n, direction]
55 | }
56 |
57 | """, value, text, use_js)
58 | except JSEvalException as e:
59 | if e.class_name == "ReferenceError" and e.description[:33] == 'ReferenceError: option not found\n':
60 | raise ValueError(f"option not found based on value:{value}, text:{text} for {elem}")
61 | raise e
62 | if use_js is False:
63 | if direction == 1:
64 | code = KeyboardCodes.UP_ARROW
65 | else:
66 | code = KeyboardCodes.DOWN_ARROW
67 | for _ in range(n):
68 | await asyncio.sleep(timeouts)
69 | async_input.base.send_keystrokes(code)
70 | await asyncio.sleep(timeouts)
71 | x, y = await elem.mid_location()
72 | await async_input.click("left", x, y)
73 | await asyncio.sleep(timeouts)
74 |
--------------------------------------------------------------------------------
/src/selenium_driverless/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/src/selenium_driverless/scripts/__init__.py
--------------------------------------------------------------------------------
/src/selenium_driverless/scripts/driver_utils.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import typing
3 |
4 |
5 | async def get_targets(cdp_exec: callable, target_getter: callable, _type: str = None, context_id: str = None,
6 | max_ws_size: int = 2 ** 20):
7 | from selenium_driverless.types.target import TargetInfo
8 | res = await cdp_exec("Target.getTargets")
9 | _infos = res["targetInfos"]
10 | infos: typing.Dict[str, TargetInfo] = {}
11 | for info in _infos:
12 | _id = info["targetId"]
13 |
14 | target = await target_getter(target_id=_id[:], max_ws_size=max_ws_size)
15 |
16 | info = await TargetInfo(info, target)
17 | if (_type is None or info.type == _type) and (context_id is None or context_id == info.browser_context_id):
18 | infos[_id] = info
19 | return infos
20 |
21 |
22 | # noinspection PyProtectedMember
23 | async def get_target(target_id: str, host: str, driver, context, loop: asyncio.AbstractEventLoop or None,
24 | is_remote: bool = False,
25 | timeout: float = 2, max_ws_size: int = 2 ** 20):
26 | from selenium_driverless.types.target import Target
27 | from selenium_driverless.sync.target import Target as SyncTarget
28 | if loop:
29 | target: Target = await SyncTarget(host=host, target_id=target_id,
30 | is_remote=is_remote, loop=loop,
31 | timeout=timeout, max_ws_size=max_ws_size, driver=driver, context=context)
32 | else:
33 | target: Target = await Target(host=host, target_id=target_id,
34 | is_remote=is_remote, loop=loop, timeout=timeout, max_ws_size=max_ws_size,
35 | driver=driver, context=context)
36 | return target
37 |
38 |
39 | async def get_cookies(target) -> typing.List[dict]:
40 | """Returns a set of dictionaries, corresponding to cookies visible in
41 | the current session.
42 |
43 | :Usage:
44 | ::
45 | target.get_cookies()
46 | """
47 | cookies = await target.execute_cdp_cmd("Network.getCookies")
48 | return cookies["cookies"]
49 |
50 |
51 | async def get_cookie(target, name) -> typing.Optional[typing.Dict]:
52 | """Get a single cookie by name. Returns the cookie if found, None if
53 | not.
54 |
55 | :Usage:
56 | ::
57 |
58 | target.get_cookie('my_cookie')
59 | """
60 | for cookie in await get_cookies(target):
61 | if cookie["name"] == name:
62 | return cookie
63 |
64 |
65 | async def delete_cookie(target, name: str, url: str = None, domain: str = None, path: str = None) -> None:
66 | """Deletes a single cookie with the given name.
67 |
68 | :Usage:
69 | ::
70 |
71 | target.delete_cookie('my_cookie')
72 | """
73 | args = {"name": name}
74 | if url:
75 | args["url"] = url
76 | if domain:
77 | args["domain"] = domain
78 | if path:
79 | args["path"] = path
80 | await target.execute_cdp_cmd("Network.deleteCookies", args)
81 |
82 |
83 | async def delete_all_cookies(target) -> None:
84 | """Delete all cookies in the scope of the session.
85 |
86 | :Usage:
87 | ::
88 |
89 | target.delete_all_cookies()
90 | """
91 | await target.execute_cdp_cmd("Network.clearBrowserCookies")
92 |
93 |
94 | # noinspection GrazieInspection
95 | async def add_cookie(target, cookie_dict, context_id: str = None) -> None:
96 | """Adds a cookie to your current session.
97 |
98 | :param cookie_dict: A dictionary object, with required keys - "name" and "value";
99 | optional keys - "path", "domain", "secure", "httpOnly", "expiry", "sameSite"
100 | :param target: the target to use for the connection
101 | :param context_id: the browserContextId to set the cookie for
102 |
103 | :Usage:
104 | ::
105 |
106 | target.add_cookie({'name' : 'foo', 'value' : 'bar'})
107 | target.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'})
108 | target.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/', 'secure' : True})
109 | target.add_cookie({'name' : 'foo', 'value' : 'bar', 'sameSite' : 'Strict'})
110 | """
111 | if "sameSite" in cookie_dict:
112 | assert cookie_dict["sameSite"] in ["Strict", "Lax", "None"]
113 | args = {"cookies": [cookie_dict]}
114 | if context_id:
115 | args["browserContextId"] = context_id
116 | await target.execute_cdp_cmd("Storage.setCookies", args)
117 |
--------------------------------------------------------------------------------
/src/selenium_driverless/scripts/prefs.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import json
3 | from functools import reduce
4 | import aiofiles
5 |
6 |
7 | def prefs_to_json(dot_prefs: dict):
8 | # prefs as {key:value}
9 | # for example {"profile.default_content_setting_values.images": 2}
10 |
11 | def undot_key(key, value):
12 | if "." in key:
13 | key, rest = key.split(".", 1)
14 | value = undot_key(rest, value)
15 | return {key: value}
16 |
17 | # undot prefs dict keys
18 | undot_prefs = reduce(
19 | lambda d1, d2: {**d1, **d2}, # merge dicts
20 | (undot_key(key, value) for key, value in dot_prefs.items()),
21 | )
22 | return undot_prefs
23 |
24 |
25 | async def write_prefs(prefs: dict, prefs_path: str):
26 | # prefs as a dict
27 | res = await asyncio.get_event_loop().run_in_executor(None, lambda:json.dumps(prefs))
28 | async with aiofiles.open(prefs_path, encoding="utf-8", mode="w+") as f:
29 | await f.write(res)
30 |
31 |
32 | async def read_prefs(prefs_path: str):
33 | # prefs as a dict
34 | async with aiofiles.open(prefs_path, encoding="utf-8", mode="r") as f:
35 | res = await f.read()
36 | res = await asyncio.get_event_loop().run_in_executor(None, lambda:json.loads(res))
37 | return res
38 |
--------------------------------------------------------------------------------
/src/selenium_driverless/scripts/switch_to.py:
--------------------------------------------------------------------------------
1 | # Licensed to the Software Freedom Conservancy (SFC) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The SFC licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | #
18 | # modified by kaliiiiiiiiii | Aurin Aegerter
19 | # all modifications are licensed under the license provided at LICENSE.md
20 |
21 | import asyncio
22 | import typing
23 | from typing import Union
24 | import warnings
25 |
26 |
27 | from selenium_driverless.types.by import By
28 | from selenium_driverless.types.alert import Alert
29 | from selenium_driverless.types.target import TargetInfo, Target
30 |
31 | from selenium_driverless.types.target import NoSuchIframe
32 | from selenium_driverless.types.webelement import WebElement, NoSuchElementException
33 |
34 |
35 | class SwitchTo:
36 | """
37 | the SwitchTo class
38 |
39 | .. warning::
40 | except for switching to targets, do not use this class
41 | """
42 | def __init__(self, context, context_id: str = None, loop: asyncio.AbstractEventLoop = None) -> None:
43 | from selenium_driverless.types.context import Context
44 | self._context: Context = context
45 | self._alert = None
46 | self._started = False
47 | self._context_id = context_id
48 | self._loop = loop
49 | # noinspection PyProtectedMember
50 | self._is_incognito = self._context._is_incognito
51 |
52 | def __await__(self):
53 | return self._init().__await__()
54 |
55 | async def _init(self):
56 | if not self._started:
57 | self._started = True
58 | return self
59 |
60 | @property
61 | async def alert(self) -> Alert:
62 | """Switches focus to an alert on the page.
63 |
64 | :Usage:
65 | ::
66 |
67 | alert = target.switch_to.alert
68 | """
69 | return await self.get_alert()
70 |
71 | async def get_alert(self, timeout: float = 5) -> Alert:
72 | """Switches focus to an alert on the page.
73 |
74 | :Usage:
75 | ::
76 |
77 | alert = target.switch_to.alert
78 | """
79 | return await self._context.current_target.get_alert(timeout=timeout)
80 |
81 | async def default_content(self, activate: bool = False) -> Target:
82 | """Switch focus to the default frame.
83 |
84 | :Usage:
85 | ::
86 |
87 | target.switch_to.default_content()
88 | """
89 | base_target = self._context.current_target.base_target
90 | if base_target:
91 | return await self.target(target_id=base_target, activate=activate)
92 |
93 | async def frame(self, frame_reference: Union[str, int, WebElement], focus:bool=True) -> None:
94 | """Switches to the specified frame
95 |
96 | :param frame_reference: the reference by ID, name, index, or WebElement
97 | :param focus: whether to emulate focus on the frame
98 | :param focus: whether to emulate focus on the iframe
99 | """
100 | warnings.warn(
101 | "driver.switch_to.iframe deprecated and not reliable use Webelement.content_document instead",
102 | DeprecationWarning)
103 | if isinstance(frame_reference, str):
104 | try:
105 | frame_reference = await self._context.find_element(By.ID, frame_reference)
106 | except NoSuchElementException:
107 | try:
108 | frame_reference = await self._context.find_element(By.NAME, frame_reference)
109 | except NoSuchElementException:
110 | pass
111 | if isinstance(frame_reference, int):
112 | try:
113 | frames = await self._context.find_elements(By.TAG_NAME, "iframe")
114 | frame_reference = frames[frame_reference]
115 | except KeyError:
116 | pass
117 |
118 | if not isinstance(frame_reference, WebElement):
119 | raise NoSuchIframe(frame_reference, f"couldn't get element by: {frame_reference}")
120 | target = await self._context.current_target.get_target_for_iframe(frame_reference)
121 | if focus:
122 | await target.focus()
123 | await self.target(target)
124 | return target
125 |
126 | async def target(self, target_id: typing.Union[str, TargetInfo, Target], activate: bool = False, focus:bool=True) -> Target:
127 | """
128 | switches to a target
129 |
130 | :param target_id: the target to switch to
131 | :param activate: whether to bring the target to the front
132 | :param focus: whether to emulate focus on the target
133 | """
134 | if isinstance(target_id, TargetInfo):
135 | self._context._current_target = target_id.Target
136 | elif isinstance(target_id, Target):
137 | self._context._current_target = target_id
138 | elif isinstance(target_id, WebElement):
139 | # noinspection PyDeprecation
140 | self._context._current_target = self.frame(target_id)
141 | else:
142 | self._context._current_target = await self._context.get_target(target_id)
143 |
144 | if activate:
145 | await self._context.current_target.activate()
146 | if focus:
147 | await self._context.current_target.focus()
148 | return self._context.current_target
149 |
150 | async def window(self, window_id: str or TargetInfo, activate: bool = False, focus:bool=True) -> Target:
151 | """
152 | switches to a window
153 |
154 | alias to :func:`SwitchTo.target Target:
160 | """creates a new tab or window
161 |
162 | :param type_hint: what kind of target to create
163 | :param url: url to start the target at
164 | :param activate: whether to bring the target to the front
165 | :param focus: whether to emulate focus on the target
166 | :param background: whether to start the target in the background
167 | """
168 | target = await self._context.new_window(type_hint=type_hint, url=url, activate=activate, focus=focus, background=background)
169 | return await self.target(target)
170 |
171 | async def parent_frame(self, activate: bool = False) -> Target:
172 | raise NotImplemented()
173 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/src/selenium_driverless/sync/__init__.py
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/alert.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.alert import Alert as AsyncAlert
5 |
6 |
7 | class Alert(AsyncAlert):
8 | def __init__(self, target, loop, timeout: float = 5):
9 | if not loop:
10 | loop = asyncio.new_event_loop()
11 | asyncio.set_event_loop(loop)
12 | self._loop = loop
13 | super().__init__(target=target, timeout=timeout)
14 | self._init()
15 |
16 | def __enter__(self):
17 | return self
18 |
19 | def __exit__(self, *args, **kwargs):
20 | self.__aexit__(*args, **kwargs)
21 |
22 | def __getattribute__(self, item):
23 | res = super().__getattribute__(item)
24 | if res is None or item == "_loop":
25 | return res
26 | loop = self._loop
27 | if loop and (not loop.is_running()):
28 | if inspect.iscoroutinefunction(res):
29 | def syncified(*args, **kwargs):
30 | return self._loop.run_until_complete(res(*args, **kwargs))
31 |
32 | return syncified
33 | if inspect.isawaitable(res):
34 | return self._loop.run_until_complete(res)
35 | return res
36 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/base_target.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.base_target import BaseTarget as AsyncBaseTarget
5 |
6 |
7 | class BaseTarget(AsyncBaseTarget):
8 | # noinspection PyShadowingBuiltins
9 | def __init__(self, host: str, is_remote: bool = False,
10 | loop: asyncio.AbstractEventLoop or None = None, timeout: float = 30,
11 | max_ws_size: int = 2 ** 20) -> None:
12 | if not loop:
13 | loop = asyncio.new_event_loop()
14 | asyncio.set_event_loop(loop)
15 | self._loop = loop
16 | super().__init__(host=host, is_remote=is_remote, loop=loop, timeout=timeout, max_ws_size=max_ws_size)
17 |
18 | def __exit__(self, *args, **kwargs):
19 | return self.__aexit__(*args, **kwargs)
20 |
21 | def __getattribute__(self, item):
22 | res = super().__getattribute__(item)
23 | if res is None or item == "_loop":
24 | return res
25 | loop = self._loop
26 | if loop and (not loop.is_running()):
27 | if inspect.iscoroutinefunction(res):
28 | def syncified(*args, **kwargs):
29 | return self._loop.run_until_complete(res(*args, **kwargs))
30 |
31 | return syncified
32 | if inspect.isawaitable(res):
33 | return self._loop.run_until_complete(res)
34 | return res
35 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/context.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.context import Context as AsyncContext
5 | from selenium_driverless.types.target import Target
6 |
7 |
8 | class Context(AsyncContext):
9 | def __init__(self, base_target: Target, driver, context_id: str = None, loop: asyncio.AbstractEventLoop = None,
10 | is_incognito: bool = False, max_ws_size: int = 2 ** 20) -> None:
11 | if not loop:
12 | loop = asyncio.new_event_loop()
13 | asyncio.set_event_loop(loop)
14 | super().__init__(base_target=base_target, driver=driver, context_id=context_id, loop=loop,
15 | is_incognito=is_incognito, max_ws_size=max_ws_size)
16 |
17 | def __enter__(self):
18 | return self
19 |
20 | def __exit__(self, *args, **kwargs):
21 | self.__aexit__(*args, **kwargs)
22 |
23 | def __getattribute__(self, item):
24 | res = super().__getattribute__(item)
25 | if res is None or item == "_loop":
26 | return res
27 | loop = self._loop
28 | if loop and (not loop.is_running()):
29 | if inspect.iscoroutinefunction(res):
30 | def syncified(*args, **kwargs):
31 | return self._loop.run_until_complete(res(*args, **kwargs))
32 |
33 | return syncified
34 | if inspect.isawaitable(res):
35 | return self._loop.run_until_complete(res)
36 | return res
37 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/pointer.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.input.pointer import Pointer as AsyncPointer, PointerType
5 |
6 |
7 | class Pointer(AsyncPointer):
8 | def __init__(self, target, pointer_type: str = PointerType.MOUSE, loop: asyncio.AbstractEventLoop = None):
9 | super().__init__(target=target, pointer_type=pointer_type)
10 | if not loop:
11 | loop = asyncio.new_event_loop()
12 | asyncio.set_event_loop(loop)
13 | self._loop = loop
14 |
15 | def __exit__(self, *args, **kwargs):
16 | return self.__aexit__(*args, **kwargs)
17 |
18 | def __getattribute__(self, item):
19 | res = super().__getattribute__(item)
20 | if res is None or item == "_loop":
21 | return res
22 | loop = self._loop
23 | if loop and (not loop.is_running()):
24 | if inspect.iscoroutinefunction(res):
25 | def syncified(*args, **kwargs):
26 | return self._loop.run_until_complete(res(*args, **kwargs))
27 |
28 | return syncified
29 | if inspect.isawaitable(res):
30 | return self._loop.run_until_complete(res)
31 | return res
32 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/switch_to.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.scripts.switch_to import SwitchTo as AsyncSwitchTo
5 |
6 |
7 | class SwitchTo(AsyncSwitchTo):
8 | def __init__(self, context, loop, context_id: str = None):
9 | super().__init__(context=context, context_id=context_id)
10 | if not loop:
11 | loop = asyncio.new_event_loop()
12 | asyncio.set_event_loop(loop)
13 | self._loop = loop
14 |
15 | def __enter__(self):
16 | return self
17 |
18 | def __exit__(self, *args, **kwargs):
19 | self.__aexit__(*args, **kwargs)
20 |
21 | def __getattribute__(self, item):
22 | res = super().__getattribute__(item)
23 | if res is None or item == "_loop":
24 | return res
25 | loop = self._loop
26 | if loop and (not loop.is_running()):
27 | if inspect.iscoroutinefunction(res):
28 | def syncified(*args, **kwargs):
29 | return self._loop.run_until_complete(res(*args, **kwargs))
30 |
31 | return syncified
32 | if inspect.isawaitable(res):
33 | return self._loop.run_until_complete(res)
34 | return res
35 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/target.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.target import Target as AsyncTarget
5 |
6 |
7 | class Target(AsyncTarget):
8 | # noinspection PyShadowingBuiltins
9 | def __init__(self, host: str, target_id: str, driver, context, is_remote: bool = False,
10 | loop: asyncio.AbstractEventLoop or None = None, timeout: float = 30,
11 | type: str = None, max_ws_size: int = 2 ** 20) -> None:
12 | super().__init__(host=host, target_id=target_id,
13 | is_remote=is_remote, loop=loop,
14 | timeout=timeout, type=type, max_ws_size=max_ws_size, driver=driver, context=context)
15 | if not loop:
16 | loop = asyncio.new_event_loop()
17 | asyncio.set_event_loop(loop)
18 | self._loop = loop
19 |
20 | def __exit__(self, *args, **kwargs):
21 | return self.__aexit__(*args, **kwargs)
22 |
23 | def __getattribute__(self, item):
24 | res = super().__getattribute__(item)
25 | if res is None or item == "_loop":
26 | return res
27 | loop = self._loop
28 | if loop and (not loop.is_running()):
29 | if inspect.iscoroutinefunction(res):
30 | def syncified(*args, **kwargs):
31 | return self._loop.run_until_complete(res(*args, **kwargs))
32 |
33 | return syncified
34 | if inspect.isawaitable(res):
35 | return self._loop.run_until_complete(res)
36 | return res
37 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/webdriver.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.options import Options as ChromeOptions
5 | from selenium_driverless.webdriver import Chrome as AsyncDriver
6 |
7 |
8 | class Chrome(AsyncDriver):
9 | def __init__(self, options: ChromeOptions = None, loop: asyncio.AbstractEventLoop = None,
10 | debug=False, max_ws_size: int = 2 ** 20):
11 | super().__init__(options=options, debug=debug, max_ws_size=max_ws_size)
12 | if not loop:
13 | loop = asyncio.new_event_loop()
14 | asyncio.set_event_loop(loop)
15 | self._loop = loop
16 | self.start_session()
17 |
18 | def __enter__(self):
19 | return self
20 |
21 | def __exit__(self, *args, **kwargs):
22 | self.__aexit__(*args, **kwargs)
23 |
24 | async def quit(self, timeout: float = 30, clean_dirs: bool = True):
25 | await super().quit(timeout=timeout, clean_dirs=clean_dirs)
26 |
27 | def __getattribute__(self, item):
28 | res = super().__getattribute__(item)
29 | if res is None or item == "_loop":
30 | return res
31 | loop = self._loop
32 | if loop and (not loop.is_running()):
33 | if inspect.iscoroutinefunction(res):
34 | def syncified(*args, **kwargs):
35 | return self._loop.run_until_complete(res(*args, **kwargs))
36 |
37 | return syncified
38 | if inspect.isawaitable(res):
39 | return self._loop.run_until_complete(res)
40 | return res
41 |
--------------------------------------------------------------------------------
/src/selenium_driverless/sync/webelement.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import inspect
3 |
4 | from selenium_driverless.types.webelement import WebElement as AsyncWebElement
5 |
6 |
7 | class WebElement(AsyncWebElement):
8 | def __init__(self, target, isolated_exec_id: int or None, frame_id: int or None, obj_id=None,
9 | node_id=None, backend_node_id: str = None, loop=None, class_name: str = None,
10 | context_id: int = None):
11 | if not loop:
12 | loop = asyncio.new_event_loop()
13 | asyncio.set_event_loop(loop)
14 | self._loop = loop
15 | super().__init__(target=target, obj_id=obj_id, node_id=node_id, loop=self._loop, context_id=context_id,
16 | class_name=class_name, backend_node_id=backend_node_id,
17 | isolated_exec_id=isolated_exec_id, frame_id=frame_id)
18 | self.__enter__()
19 |
20 | @property
21 | def class_name(self):
22 | return self._class_name
23 |
24 | def __enter__(self):
25 | return self.__aenter__
26 |
27 | def __exit__(self, *args, **kwargs):
28 | self.__aexit__(*args, **kwargs)
29 |
30 | def __getattribute__(self, item):
31 | res = super().__getattribute__(item)
32 | if res is None or item == "_loop":
33 | return res
34 | loop = self._loop
35 | if loop and (not loop.is_running()):
36 | if inspect.iscoroutinefunction(res):
37 | def syncified(*args, **kwargs):
38 | return self._loop.run_until_complete(res(*args, **kwargs))
39 |
40 | return syncified
41 | if inspect.isawaitable(res):
42 | return self._loop.run_until_complete(res)
43 | return res
44 |
--------------------------------------------------------------------------------
/src/selenium_driverless/types/__init__.py:
--------------------------------------------------------------------------------
1 | class JSEvalException(Exception):
2 | def __init__(self, exception_details):
3 | super().__init__()
4 | self.exc_id = exception_details['exceptionId']
5 | self.text = exception_details["text"]
6 | self.line_n = exception_details['lineNumber']
7 | self.column_n = exception_details['columnNumber']
8 | exc = exception_details["exception"]
9 | self.type = exc["type"]
10 | self.subtype = exc["subtype"]
11 | self.class_name = exc["className"]
12 | self.description = exc["description"]
13 | self.obj_id = exc["objectId"]
14 |
15 | def __str__(self):
16 | return self.description
17 |
--------------------------------------------------------------------------------
/src/selenium_driverless/types/alert.py:
--------------------------------------------------------------------------------
1 | # Licensed to the Software Freedom Conservancy (SFC) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The SFC licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | #
18 | # modified by kaliiiiiiiiii | Aurin Aegerter
19 | # all modifications are licensed under the license provided at LICENSE.md
20 |
21 | """The Alert implementation."""
22 | import asyncio
23 | import warnings
24 |
25 |
26 | # noinspection PyProtectedMember
27 | class Alert:
28 | """Allows to work with alerts.
29 |
30 | Use this class to interact with alert prompts. It contains methods for dismissing,
31 | accepting, inputting, and getting text from alert prompts.
32 |
33 | Accepting / Dismissing alert prompts::
34 |
35 | Alert(target).accept()
36 | Alert(target).dismiss()
37 |
38 | Inputting a value into an alert prompt::
39 |
40 | name_prompt = Alert(target)
41 | name_prompt.send_keys("Willian Shakesphere")
42 | name_prompt.accept()
43 |
44 |
45 | Reading the text of a prompt for verification::
46 |
47 | alert_text = Alert(target).text
48 | self.assertEqual("Do you wish to quit?", alert_text)
49 | """
50 |
51 | def __init__(self, target, timeout: float = 5) -> None:
52 | """Creates a new Alert.
53 |
54 | :param target: The Target instance which performs user actions.
55 | """
56 | from selenium_driverless.types.target import Target
57 | self.target: Target = target
58 | self._timeout = timeout
59 | self._started = False
60 |
61 | def __await__(self):
62 | return self._init().__await__()
63 |
64 | async def _init(self):
65 | if not self._started:
66 | if not self.target._alert:
67 | try:
68 | await self.target.wait_for_cdp("Page.javascriptDialogOpening", self._timeout)
69 | except (asyncio.TimeoutError, TimeoutError):
70 | self._warn_not_detected()
71 | self._started = True
72 | return self
73 |
74 | @staticmethod
75 | def _warn_not_detected():
76 | warnings.warn("clouldn't detect if dialog is shown, you might execute Page.enable before")
77 |
78 | @property
79 | def text(self) -> str:
80 | """Gets the text of the Alert."""
81 | if self.target._alert:
82 | return self.target._alert["message"]
83 | self._warn_not_detected()
84 |
85 | @property
86 | def url(self) -> str:
87 | if self.target._alert:
88 | return self.target._alert["url"]
89 | self._warn_not_detected()
90 |
91 | @property
92 | def type(self) -> str:
93 | if self.target._alert:
94 | return self.target._alert["type"]
95 | self._warn_not_detected()
96 |
97 | @property
98 | def has_browser_handler(self) -> bool:
99 | if self.target._alert:
100 | return self.target._alert["hasBrowserHandler"]
101 | self._warn_not_detected()
102 |
103 | @property
104 | def default_prompt(self):
105 | if self.target._alert:
106 | return self.target._alert["defaultPrompt"]
107 | self._warn_not_detected()
108 |
109 | async def dismiss(self) -> None:
110 | """Dismisses the alert available."""
111 | await self.target.execute_cdp_cmd("Page.handleJavaScriptDialog", {"accept": False})
112 |
113 | async def accept(self) -> None:
114 | """Accepts the alert available."""
115 | await self.target.execute_cdp_cmd("Page.handleJavaScriptDialog", {"accept": True})
116 |
117 | # noinspection PyPep8Naming
118 | async def send_keys(self, keysToSend: str) -> None:
119 | """Send Keys to the Alert.
120 |
121 | :param keysToSend: The text to be sent to Alert.
122 | """
123 | await self.target.execute_cdp_cmd("Page.handleJavaScriptDialog", {"accept": True, "promptText": keysToSend})
124 |
--------------------------------------------------------------------------------
/src/selenium_driverless/types/base_target.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import time
3 | import typing
4 |
5 | import aiohttp
6 | import websockets
7 | from cdp_socket.exceptions import CDPError
8 | from cdp_socket.socket import SingleCDPSocket
9 |
10 |
11 | class BaseTarget:
12 | """the baseTarget for the ChromeInstance
13 | represents a connection to the whole browser.
14 |
15 | .. note::
16 | commands executed on BaseTarget usually are on a global scope over the whole Chrome instance.
17 | unfortunately, not all are supported
18 |
19 | """
20 |
21 | # noinspection PyMissingConstructor
22 | def __init__(self, host: str, is_remote: bool = False,
23 | loop: asyncio.AbstractEventLoop or None = None, timeout: float = 30,
24 | max_ws_size: int = 2 ** 20) -> None:
25 | self._socket = None
26 |
27 | self._is_remote = is_remote
28 | self._host = host
29 | self._id = "BaseTarget"
30 |
31 | self._loop = loop
32 | self._started = False
33 | self._timeout = timeout
34 | self._max_ws_size = max_ws_size
35 | self._downloads_paths = {}
36 |
37 | def __repr__(self):
38 | return f'<{type(self).__module__}.{type(self).__name__} (target_id="{self.id}", host="{self._host}")>'
39 |
40 | @property
41 | def id(self):
42 | return self._id
43 |
44 | @property
45 | async def type(self):
46 | return "BaseTarget"
47 |
48 | @property
49 | def socket(self) -> SingleCDPSocket:
50 | """the cdp-socket for the connection"""
51 | return self._socket
52 |
53 | async def __aenter__(self):
54 | await self._init()
55 | return self
56 |
57 | def __enter__(self):
58 | return self
59 |
60 | async def __aexit__(self, exc_type, exc_val, exc_tb):
61 | await self.close()
62 |
63 | def __exit__(self, exc_type, exc_val, exc_tb):
64 | pass
65 |
66 | def __await__(self):
67 | return self._init().__await__()
68 |
69 | async def _init(self):
70 | if not self._started:
71 | start = time.perf_counter()
72 | url = f"http://{self._host}/json/version"
73 | while True:
74 | try:
75 | async with aiohttp.ClientSession() as session:
76 | res = await session.get(url, timeout=10)
77 | _json = await res.json()
78 | break
79 | except (aiohttp.ClientError, asyncio.TimeoutError, OSError):
80 | if (time.perf_counter() - start) > self._timeout:
81 | raise asyncio.TimeoutError(
82 | f"Couldn't connect to chrome within {self._timeout} seconds")
83 | self._socket = await SingleCDPSocket(websock_url=_json["webSocketDebuggerUrl"], timeout=self._timeout,
84 | loop=self._loop, max_size=self._max_ws_size)
85 | self._started = True
86 | return self
87 |
88 | async def close(self) -> None:
89 | try:
90 | await self._socket.close()
91 | except websockets.ConnectionClosedError:
92 | pass
93 | except CDPError as e:
94 | if e.code == -32000 and e.message == 'Command can only be executed on top-level targets':
95 | pass
96 | else:
97 | raise e
98 |
99 | async def wait_for_cdp(self, event: str, timeout: float or None = None):
100 | """wait for an event
101 | see :func:`Target.wait_for_cdp ` for reference
102 | """
103 | if not self.socket:
104 | await self._init()
105 | return await self.socket.wait_for(event, timeout=timeout)
106 |
107 | async def add_cdp_listener(self, event: str, callback: typing.Callable[[dict], any]):
108 | """
109 | add a listener for a CDP event
110 | see :func:`Target.add_cdp_listener ` for reference
111 | """
112 | if not self.socket:
113 | await self._init()
114 | self.socket.add_listener(method=event, callback=callback)
115 |
116 | async def remove_cdp_listener(self, event: str, callback: typing.Callable[[dict], any]):
117 | """
118 | remove a listener for a CDP event
119 | see :func:`Target.remove_cdp_listener ` for reference
120 | """
121 | if not self.socket:
122 | await self._init()
123 | self.socket.remove_listener(method=event, callback=callback)
124 |
125 | async def get_cdp_event_iter(self, event: str) -> typing.AsyncIterable[dict]:
126 | """
127 | iterate over CDP events on the current target
128 | see :func:`Target.get_cdp_event_iter ` for reference
129 | """
130 | if not self.socket:
131 | await self._init()
132 | return self.socket.method_iterator(method=event)
133 |
134 | async def execute_cdp_cmd(self, cmd: str, cmd_args: dict or None = None,
135 | timeout: float or None = 10) -> dict:
136 | """Execute Chrome Devtools Protocol command and get returned result
137 | see :func:`Target.execute_cdp_cmd ` for reference
138 | """
139 | if not self.socket:
140 | await self._init()
141 | if cmd == "Browser.setDownloadBehavior":
142 | path = cmd_args.get("downloadPath")
143 | if path:
144 | self._downloads_paths[cmd_args.get("browserContextId", "DEFAULT")] = path
145 | result = await self.socket.exec(method=cmd, params=cmd_args, timeout=timeout)
146 | return result
147 |
148 | def downloads_dir_for_context(self, context_id: str = "DEFAULT") -> str:
149 | """get the default download directory for a specific context
150 |
151 | :param context_id: the id of the context to get the directory for
152 | """
153 | return self._downloads_paths.get(context_id)
154 |
--------------------------------------------------------------------------------
/src/selenium_driverless/types/by.py:
--------------------------------------------------------------------------------
1 | # Licensed to the Software Freedom Conservancy (SFC) under one
2 | # or more contributor license agreements. See the NOTICE file
3 | # distributed with this work for additional information
4 | # regarding copyright ownership. The SFC licenses this file
5 | # to you under the Apache License, Version 2.0 (the
6 | # "License"); you may not use this file except in compliance
7 | # with the License. You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | #
18 | # edited by github/kaliiiiiiiiii
19 | # all modifications are licensed under the license provided at LICENSE.md
20 |
21 |
22 | class By:
23 | """Set of supported locator strategies."""
24 | ID = "id"
25 | """"""
26 | NAME = "name"
27 | """"""
28 | XPATH = "xpath"
29 | """"""
30 | TAG_NAME = "tag name"
31 | """"""
32 | CLASS_NAME = "class name"
33 | """"""
34 | CSS_SELECTOR = "css selector"
35 | """"""
36 | CSS = "css selector"
37 | """alias to By.CSS_SELECTOR"""
38 |
--------------------------------------------------------------------------------
/src/selenium_driverless/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaliiiiiiiiii/Selenium-Driverless/ca333fd88b0b7722ac128e08deccbb5ffbd66b39/src/selenium_driverless/utils/__init__.py
--------------------------------------------------------------------------------
/src/selenium_driverless/utils/utils.py:
--------------------------------------------------------------------------------
1 | # from https://github.com/ultrafunkamsterdam/undetected-chromedriver/blob/1c704a71cf4f29181a59ecf19ddff32f1b4fbfc0/undetected_chromedriver/__init__.py#L844
2 | # edited by kaliiiiiiiiii | Aurin Aegerter
3 | import asyncio
4 | import sys
5 | import typing
6 | import os
7 | import time
8 |
9 | import socket
10 | import selenium
11 | import selenium_driverless
12 | from contextlib import closing
13 | import aiofiles
14 | from platformdirs import user_data_dir
15 | from selenium_driverless import __version__
16 |
17 | IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2"))
18 | T_JSON_DICT = typing.Dict[str, typing.Any]
19 |
20 | DATA_DIR = user_data_dir(appname="selenium-driverless", appauthor="kaliiiiiiiiii", ensure_exists=True)
21 | LICENSE = '\nThis project is licenced under "Attribution-NonCommercial-ShareAlike" as per https://github.com/kaliiiiiiiiii/Selenium-Driverless/blob/master/LICENSE.md#license\n'
22 |
23 |
24 | def find_chrome_executable():
25 | """
26 | Finds the Chrome, Chrome beta, Chrome canary, Chromium executable
27 |
28 | Returns
29 | -------
30 | executable_path : str
31 | the full file path to found executable
32 |
33 | """
34 | candidates = set()
35 | if IS_POSIX:
36 | for item in os.environ.get("PATH", "").split(os.pathsep):
37 | for subitem in (
38 | "google-chrome",
39 | "chromium",
40 | "chromium-browser",
41 | "chrome",
42 | "google-chrome-stable",
43 | ):
44 | candidates.add(os.sep.join((item, subitem)))
45 | if "darwin" in sys.platform:
46 | candidates.update(
47 | [
48 | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
49 | "/Applications/Chromium.app/Contents/MacOS/Chromium",
50 | ]
51 | )
52 | else:
53 | for item in map(
54 | os.environ.get,
55 | ("PROGRAMFILES", "PROGRAMFILES(X86)", "LOCALAPPDATA", "PROGRAMW6432"),
56 | ):
57 | if item is not None:
58 | for subitem in (
59 | "Google/Chrome/Application",
60 | "Google/Chrome Beta/Application",
61 | "Google/Chrome Canary/Application",
62 | ):
63 | candidates.add(os.sep.join((item, subitem, "chrome.exe")))
64 | for candidate in candidates:
65 | if os.path.exists(candidate) and os.access(candidate, os.X_OK):
66 | return os.path.normpath(candidate)
67 | raise FileNotFoundError("Couldn't find installed Chrome or Chromium executable")
68 |
69 |
70 | def sel_driverless_path():
71 | return os.path.dirname(selenium_driverless.__file__) + "/"
72 |
73 |
74 | def sel_path():
75 | return os.path.dirname(selenium.__file__) + "/"
76 |
77 |
78 | async def read(filename: str, encoding: str = "utf-8", sel_root: bool = False):
79 | if sel_root:
80 | path = sel_driverless_path() + filename
81 | else:
82 | path = filename
83 | async with aiofiles.open(path, encoding=encoding) as f:
84 | return await f.read()
85 |
86 |
87 | async def write(filename: str, content: str, encoding: str = "utf-8", sel_root: bool = False):
88 | if sel_root:
89 | path = sel_driverless_path() + filename
90 | else:
91 | path = filename
92 | async with aiofiles.open(path, "w+", encoding=encoding) as f:
93 | return await f.write(content)
94 |
95 |
96 | def random_port(host: str = None):
97 | if not host:
98 | host = ''
99 | with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
100 | s.bind((host, 0))
101 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
102 | return s.getsockname()[1]
103 |
104 |
105 | def check_timeout(start_monotonic: float, timeout: float):
106 | if (time.perf_counter() - start_monotonic) > timeout:
107 | raise asyncio.TimeoutError(f"driver.quit took longer than timeout: {timeout}")
108 |
109 |
110 | async def is_first_run():
111 | path = DATA_DIR + "/is_first_run"
112 | if os.path.isfile(path):
113 | res = await read(path, sel_root=False)
114 | if res == __version__:
115 | return False
116 | else:
117 | await write(path, __version__, sel_root=False)
118 | print(LICENSE, file=sys.stderr)
119 | # new version
120 | return None
121 | else:
122 | # first run
123 | print(LICENSE, file=sys.stderr)
124 | await write(path, __version__, sel_root=False)
125 | return True
126 |
127 |
128 | async def get_default_ua():
129 | path = DATA_DIR + "/useragent"
130 | if os.path.isfile(path):
131 | res = await read(path, sel_root=False)
132 | return res
133 |
134 |
135 | async def set_default_ua(ua: str):
136 | path = DATA_DIR + "/useragent"
137 | await write(path, ua, sel_root=False)
138 |
139 |
140 | background_tasks = set()
141 |
142 |
143 | def safe_wrap_fut(fn: typing.Awaitable):
144 | fut = asyncio.Future()
145 |
146 | async def helper_fn(_fut: asyncio.Future, _fn: typing.Awaitable):
147 | try:
148 | result = await _fn
149 | try:
150 | fut.set_result(result)
151 | except asyncio.InvalidStateError:
152 | pass
153 | except Exception as e:
154 | try:
155 | fut.set_exception(e)
156 | except asyncio.InvalidStateError:
157 | pass
158 |
159 | task = asyncio.ensure_future(helper_fn(fut, fn))
160 |
161 | # keep strong references
162 | background_tasks.add(task)
163 | task.add_done_callback(background_tasks.discard)
164 | return fut
165 |
--------------------------------------------------------------------------------
/sync_main.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless.sync import webdriver
2 |
3 | options = webdriver.ChromeOptions()
4 | with webdriver.Chrome(options=options) as driver:
5 | driver.get('https://abrahamjuliot.github.io/creepjs/')
6 | print(driver.title)
7 |
--------------------------------------------------------------------------------
/tests/antibots/test_bet365.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless.types.by import By
2 | from cdp_patches.input import AsyncInput
3 | from selenium_driverless.types.webelement import NoSuchElementException
4 | import asyncio
5 | import pytest
6 |
7 |
8 | async def bet365_test(driver, async_input: AsyncInput = None):
9 | async def click_login(timeout: float = 30):
10 | login_button = await driver.find_element(By.XPATH, value='//*[@class="hm-MainHeaderRHSLoggedOutWide_Join "]',
11 | timeout=timeout)
12 | if async_input is None:
13 | await login_button.click()
14 | else:
15 | x, y = await login_button.mid_location()
16 | await async_input.click("left", x, y)
17 |
18 | await driver.focus()
19 | await driver.get('https://www.365365824.com/#/IP/B16', wait_load=True)
20 | await asyncio.sleep(1)
21 | await click_login()
22 | await asyncio.sleep(1)
23 | try:
24 | await click_login(timeout=3)
25 | except (NoSuchElementException, asyncio.TimeoutError):
26 | pass
27 | await asyncio.sleep(3)
28 | url = await driver.current_url
29 | assert url[:31] == "https://www.365365824.com/#/ME/"
30 |
31 |
32 | @pytest.mark.skip(reason="Passes anyways if headless passes")
33 | @pytest.mark.asyncio
34 | @pytest.mark.skip_offline
35 | async def test_headfull_bet365(driver):
36 | await bet365_test(driver)
37 |
38 |
39 | @pytest.mark.skip(reason="DNS_PROBE_FINISHED_NXDOMAIN")
40 | @pytest.mark.asyncio
41 | @pytest.mark.skip_offline
42 | async def test_headless_bet365(h_driver):
43 | await bet365_test(h_driver)
44 |
--------------------------------------------------------------------------------
/tests/antibots/test_brotector.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import pytest
3 | from selenium_driverless.types.by import By
4 | import typing
5 | from selenium_driverless.types.target import Target
6 | from cdp_patches.input import AsyncInput
7 |
8 |
9 | async def detect(target: Target, cdp_patches_input: typing.Union[AsyncInput, typing.Literal[False, None]] = False):
10 | script = """
11 | await brotector.init_done;
12 | return brotector.detections
13 | """
14 | await target.get("https://kaliiiiiiiiii.github.io/brotector/")
15 | await asyncio.sleep(0.5)
16 | click_target = await target.find_element(By.ID, "clickHere")
17 | if cdp_patches_input:
18 | x, y = await click_target.mid_location()
19 | await cdp_patches_input.click("left", x, y)
20 | else:
21 | await click_target.click()
22 | await asyncio.sleep(0.5)
23 | for _ in range(2):
24 | detections = await target.eval_async(script, unique_context=False)
25 | assert len(detections) == 0
26 |
27 |
28 | @pytest.mark.skip(reason="CDP isn't integrated automatically yet")
29 | @pytest.mark.asyncio
30 | @pytest.mark.skip_offline
31 | async def test_driverless(driver):
32 | await detect(driver.current_target, cdp_patches_input=False)
33 |
34 |
35 | @pytest.mark.asyncio
36 | @pytest.mark.skip_offline
37 | async def test_driverless_with_cdp_patches(driver):
38 | await detect(driver.current_target, cdp_patches_input=await AsyncInput(browser=driver))
39 |
40 |
41 | @pytest.mark.skip(reason="Headless will always fail")
42 | @pytest.mark.asyncio
43 | @pytest.mark.skip_offline
44 | async def test_headless(h_driver):
45 | await detect(h_driver.current_target, cdp_patches_input=False)
46 |
--------------------------------------------------------------------------------
/tests/antibots/test_cloudfare.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import pytest
3 | from selenium_driverless.types.by import By
4 | from selenium_driverless.types.webelement import NoSuchElementException
5 |
6 |
7 | @pytest.mark.asyncio
8 | @pytest.mark.skip_offline
9 | async def test_bypass_turnstile(h_driver, subtests):
10 | await h_driver.get("https://nopecha.com/demo/turnstile")
11 | await asyncio.sleep(0.5)
12 |
13 | # some random mouse-movements over iframes
14 | pointer = h_driver.current_pointer
15 | await pointer.move_to(500, 200, smooth_soft=60, total_time=0.5)
16 | await pointer.move_to(20, 50, smooth_soft=60, total_time=0.5)
17 | await pointer.move_to(8, 45, smooth_soft=60, total_time=0.5)
18 | await pointer.move_to(500, 200, smooth_soft=60, total_time=0.5)
19 | await pointer.move_to(166, 206, smooth_soft=60, total_time=0.5)
20 | await pointer.move_to(200, 205, smooth_soft=60, total_time=0.5)
21 |
22 | wrappers = await h_driver.find_elements(By.XPATH, '//*[@class="turnstile"]')
23 | await asyncio.sleep(0.5)
24 |
25 | for wrapper in wrappers:
26 | with subtests.test(wrapper=wrapper):
27 | # filter out correct iframe document
28 | inner = await wrapper.execute_script("return obj.children[0].children[0]")
29 | if await inner.is_visible():
30 | shadow_document = await inner.shadow_root
31 |
32 | iframe = await shadow_document.find_element(By.CSS_SELECTOR, "iframe")
33 | content_document = await iframe.content_document
34 | body = await content_document.execute_script("return document.body", unique_context=True)
35 | nested_shadow_document = await body.shadow_root
36 | try:
37 | elem = await nested_shadow_document.find_element(By.CSS_SELECTOR, "#success", timeout=4)
38 | if not await elem.is_visible():
39 | raise asyncio.TimeoutError()
40 | # already passed
41 | except (NoSuchElementException, asyncio.TimeoutError):
42 | checkbox = await nested_shadow_document.find_element(By.CSS_SELECTOR, "input[type='checkbox']",
43 | timeout=10)
44 | await checkbox.click(move_to=True)
45 | await asyncio.sleep(4)
46 | elem = await nested_shadow_document.find_element(By.CSS_SELECTOR, "#success", timeout=20)
47 | assert await elem.is_visible()
48 |
--------------------------------------------------------------------------------
/tests/antibots/test_selenium_detector.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless.types.by import By
2 | from selenium_driverless.types.target import Target
3 | import pytest
4 |
5 |
6 | @pytest.mark.skip_offline
7 | @pytest.mark.asyncio
8 | async def test_detector(h_driver: Target):
9 | await h_driver.get('https://hmaker.github.io/selenium-detector/')
10 | elem = await h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-token")
11 | await elem.write(await h_driver.execute_script('return window.token', unique_context=False))
12 | elem2 = await h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-asynctoken")
13 | async_token = await h_driver.eval_async('return await window.getAsyncToken()', unique_context=False)
14 | await elem2.write(async_token)
15 | elem3 = await h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-test")
16 | await elem3.click()
17 | passed = await h_driver.find_element(By.XPATH, '//*[@id="chromedriver-test-container"]/span')
18 | text = await passed.text
19 | assert text == "Passed!"
20 |
--------------------------------------------------------------------------------
/tests/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | driverless-fp-collector demo
6 |
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
driverless-fp-collector demo
21 |
Source-Code
22 |
23 |
24 |
50 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import sys
3 |
4 | #sys.path.append(str(Path(__file__).parent.parent.absolute()) + "/src")
5 |
6 | import pytest
7 | import pytest_asyncio
8 | import typing
9 | import socket
10 | from selenium_driverless import webdriver
11 | from selenium_driverless.sync import webdriver as sync_webdriver
12 | from server_for_testing import Server
13 |
14 | no_headless = True
15 | x = y = 30
16 | h_x = h_y = -2400 # https://issues.chromium.org/issues/367764867
17 | width = 1024
18 | height = 720
19 |
20 | if no_headless:
21 | # noinspection PyRedeclaration
22 | h_x, h_y = x, y
23 |
24 | try:
25 | socket.setdefaulttimeout(2)
26 | socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("8.8.8.8", 53))
27 | offline = False
28 | except socket.error as ex:
29 | offline = True
30 |
31 | skip_offline = pytest.mark.skipif(offline, reason="can only run online")
32 |
33 |
34 | def mk_opt(headless=False):
35 | options = webdriver.ChromeOptions()
36 | options.add_argument(f"--window-size={width},{height}")
37 | if headless and not no_headless:
38 | _x, _y = h_x, h_y
39 | else:
40 | _x, _y = x, y
41 | options.add_argument(f"--window-position={_x},{_y}")
42 | return options
43 |
44 |
45 | @pytest_asyncio.fixture
46 | async def driver() -> typing.Generator[webdriver.Chrome, None, None]:
47 | options = mk_opt()
48 | debug = False
49 | # options.add_argument("--log-level=0")
50 | async with webdriver.Chrome(options=options, debug=debug) as _driver:
51 | await _driver.set_window_rect(x, y, width, height)
52 | yield _driver
53 |
54 |
55 | @pytest_asyncio.fixture
56 | async def h_driver() -> typing.Generator[webdriver.Chrome, None, None]:
57 | options = mk_opt(headless=True)
58 | options.headless = not no_headless
59 | async with webdriver.Chrome(options=options) as _driver:
60 | await _driver.set_window_rect(h_x, h_y, width, height)
61 | yield _driver
62 |
63 |
64 | @pytest.fixture
65 | def sync_driver() -> typing.Generator[webdriver.Chrome, None, None]:
66 | options = mk_opt()
67 | with sync_webdriver.Chrome(options=options) as _driver:
68 | driver.set_window_rect(x, y, width, height)
69 | yield _driver
70 |
71 |
72 | @pytest.fixture
73 | def sync_h_driver() -> typing.Generator[webdriver.Chrome, None, None]:
74 | options = mk_opt(headless=True)
75 | options.headless = not no_headless
76 | with sync_webdriver.Chrome(options=options) as _driver:
77 | _driver.set_window_rect(h_x, h_y, width, height)
78 | yield _driver
79 |
80 |
81 | def pytest_runtest_setup(item):
82 | if offline:
83 | for _ in item.iter_markers(name="skip_offline"):
84 | pytest.skip("Test requires being online")
85 |
86 |
87 | @pytest.fixture(scope="module", autouse=True)
88 | def test_server():
89 | with Server() as server:
90 | yield server
91 |
--------------------------------------------------------------------------------
/tests/fp.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from selenium_driverless import webdriver
4 | import asyncio
5 | import os
6 | import pathlib
7 | import jsondiff
8 | import pprint
9 |
10 | with open(pathlib.Path(os.getcwd() + "/assets/clean.json"), "r", encoding="utf-8") as f:
11 | clean = json.load(f)
12 |
13 |
14 | async def get_fp(driver: webdriver.Chrome):
15 | await driver.get(os.getcwd() + "/assets/index.html")
16 | js = """
17 | var elem = document.documentElement;
18 | function callback(e){
19 | window.fp_click_callback(e)
20 | elem.removeEventListener("mousedown", this);
21 | }
22 | var data = getFingerprint(true, false);
23 | elem.addEventListener("mousedown", callback);
24 | return JSON.stringify(await data)
25 | """
26 | await asyncio.sleep(1)
27 | fut = asyncio.ensure_future(driver.eval_async(js, timeout=10, unique_context=False))
28 | await asyncio.sleep(1)
29 | pointer = driver.current_pointer
30 | await pointer.down(x=10, y=10)
31 | fp = json.loads(await fut)
32 | return fp
33 |
34 |
35 | def clean_passthrough(fp: dict):
36 | # network speed can be different
37 | del fp["connection"]
38 |
39 | # window size can be different
40 | del fp['innerHeight']
41 | del fp['innerWidth']
42 | del fp['outerHeight']
43 | del fp['outerWidth']
44 |
45 | del fp["is_bot"]
46 |
47 | return fp
48 |
49 |
50 | async def base_driver():
51 | options = webdriver.ChromeOptions()
52 | env = os.environ.copy()
53 | options.env = env
54 | async with webdriver.Chrome(options=options) as driver:
55 | return await get_fp(driver)
56 |
57 |
58 | async def main():
59 | global clean
60 | default, = await asyncio.gather(
61 | base_driver()
62 | )
63 | clean = clean_passthrough(clean)
64 | default = clean_passthrough(default)
65 | default_diff = jsondiff.diff(default, clean)
66 | pprint.pprint(default_diff)
67 |
68 |
69 | asyncio.run(main())
70 |
--------------------------------------------------------------------------------
/tests/html/test_html_source.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.asyncio
5 | async def test_get_source(h_driver, test_server):
6 | url = test_server.url
7 | target = h_driver.current_target
8 | await target.get(url)
9 | source = await target.page_source
10 | assert source == 'Hello World!'
11 |
12 |
13 | @pytest.mark.asyncio
14 | async def test_get_source_with_shadow(h_driver, subtests, test_server):
15 | url = test_server.url
16 | target = h_driver.current_target
17 |
18 | await target.get(url)
19 | await target.execute_script("""
20 | const host = document.createElement('div');
21 | document.body.appendChild(host);
22 | let shadowRoot = host.attachShadow({ mode: 'closed' });
23 | shadowRoot.innerHTML = `Isolated content
`;
24 | """, unique_context=False)
25 | source = await target.page_source
26 | mhtml = await target.snapshot()
27 | assert 'Hello World!' in mhtml
28 | assert source == 'Hello World!
'
29 |
--------------------------------------------------------------------------------
/tests/html/test_relative_find_elem.py:
--------------------------------------------------------------------------------
1 | from selenium_driverless.types.by import By
2 | import pytest
3 |
4 | dl_len = 12
5 | relative_html = ""
6 | for i in range(1, dl_len + 1):
7 | relative_html += f'Element{i} \n'
8 |
9 |
10 | @pytest.mark.asyncio
11 | async def relative_test(driver, subtests, by, value):
12 | # Load the static HTML content
13 | await driver.current_target.set_source(relative_html)
14 |
15 | # Find the elements as per the main function logic
16 | dl_list = await driver.find_elements(By.CSS_SELECTOR, 'dl.test_cls')
17 | with subtests.test():
18 | assert len(dl_list) == dl_len
19 | for idx, dl in enumerate(dl_list):
20 | idx += 1
21 | elem = await dl.find_element(by, value)
22 |
23 | texts = [await dl.text, await elem.text]
24 | for text in texts:
25 | with subtests.test():
26 | assert text == f'Element{idx}'
27 |
28 |
29 | @pytest.mark.asyncio
30 | async def test_relative_xpath(h_driver, subtests):
31 | # also includes By.ID, By.CLASS_NAME, By.NAME - see https://github.com/kaliiiiiiiiii/Selenium-Driverless/blob/91273f1dd5bf0fea8c88f478cb209e6326e3ed34/src/selenium_driverless/types/webelement.py#L288-L296
32 | await relative_test(h_driver, subtests, By.XPATH, './/span[@class="test_sub_cls"]')
33 |
34 |
35 | @pytest.mark.asyncio
36 | async def test_relative_tag_name(h_driver, subtests):
37 | await relative_test(h_driver, subtests, By.TAG_NAME, 'span')
38 |
39 |
40 | @pytest.mark.asyncio
41 | async def test_relative_tag_name(h_driver, subtests):
42 | await relative_test(h_driver, subtests, By.TAG_NAME, 'span')
43 |
44 |
45 | @pytest.mark.asyncio
46 | async def test_relative_css(h_driver, subtests):
47 | # alias to By.CSS_SELECTOR
48 | await relative_test(h_driver, subtests, By.CSS, '.test_sub_cls')
49 |
--------------------------------------------------------------------------------
/tests/interaction/test_select.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import asyncio
3 | from selenium_driverless.types.target import Target
4 | from selenium_driverless.webdriver import Chrome
5 | from selenium_driverless.types.by import By
6 | from selenium_driverless.input.utils import select
7 |
8 | from cdp_patches.input import AsyncInput
9 |
10 | # puppeteer:
11 | # https://github.com/puppeteer/puppeteer/blob/409d244aed480fbb5254f852afb16bd101692f9a/packages/puppeteer-core/src/api/ElementHandle.ts#L919-L961
12 | # selenium:
13 | # https://source.chromium.org/chromium/chromium/src/+/main:third_party/selenium-atoms/atoms.cc;l=706;drc=81d53181af5d6645d8b6ea5cca60c059edae5a3c
14 | # playwright
15 | # https://github.com/microsoft/playwright/blob/1a7d6749daa18cb26c40bc58abb56af9ffe69f02/packages/playwright-core/src/server/injected/injectedScript.ts#L594-L637
16 | # drissionpage
17 | # https://github.com/g1879/DrissionPage/blob/0ec765e28ae0bc19fd7bca3ce2a00f8cb8337c6b/DrissionPage/_units/selector.py#L251-L269
18 |
19 | values = ["rat", "bird", "dog", "cat", "cat"]
20 |
21 | select_html = """
22 |
23 | --Please Select --
24 | spam
25 | spam
26 | spam
27 | spam
28 | spam
29 | spam
30 | spam
31 | spam
32 | spam
33 | spam
34 | spam
35 | spam
36 | spam
37 | spam
38 | spam
39 | spam
40 | spam
41 | Dog
42 | spam
43 | Cat
44 | Bird
45 | spam
46 | Rat
47 | spam
48 | spam
49 |
50 | """
51 |
52 | track_js = """
53 | globalThis.selected = undefined;
54 | globalThis.trusted = undefined;
55 | var elem = document.getElementById("animals")
56 | elem.addEventListener("change", (e)=>{globalThis.selected=e.target.value; globalThis.trusted = e.isTrusted});
57 | """
58 |
59 |
60 | # doesn't have an effect
61 | async def enter(tab: Target):
62 | # press enter on a TAB
63 | await asyncio.sleep(0.05)
64 | # press enter
65 | key_event = {
66 | "type": "keyDown",
67 | "code": "Enter",
68 | "windowsVirtualKeyCode": 13,
69 | "key": "Enter",
70 | "modifiers": 0
71 | }
72 | await tab.execute_cdp_cmd("Input.dispatchKeyEvent", key_event)
73 | await asyncio.sleep(0.05)
74 | key_event["type"] = "keyUp"
75 | await tab.execute_cdp_cmd("Input.dispatchKeyEvent", key_event)
76 |
77 |
78 | # doesn't have an effect
79 | async def down(tab: Target):
80 | # press enter on a TAB
81 | await asyncio.sleep(0.05)
82 | # press enter
83 | key_event = {
84 | "type": "keyDown",
85 | "code": "ArrowDown",
86 | "windowsVirtualKeyCode": 0x28,
87 | "nativeVirtualKeyCode": 0x28,
88 | "key": "ArrowDow",
89 | "keyIdentifier": "U+2193",
90 | "modifiers": 0,
91 | "commands": ["MoveDown"],
92 | "isSystemKey": False
93 | }
94 | await tab.execute_cdp_cmd("Input.dispatchKeyEvent", key_event)
95 | await asyncio.sleep(0.01)
96 | key_event["type"] = "keyUp"
97 | await tab.execute_cdp_cmd("Input.dispatchKeyEvent", key_event)
98 |
99 |
100 | async def add_elem(driver: Chrome):
101 | await driver.current_target.set_source(select_html)
102 | await driver.execute_script(track_js, unique_context=True)
103 |
104 |
105 | async def select_test(driver, subtests, headfull=False):
106 | await add_elem(driver)
107 | async_input = None
108 | if headfull:
109 | async_input = await AsyncInput(browser=driver)
110 |
111 | elem = await driver.find_element(By.ID, "animals")
112 |
113 | for i in range(9):
114 | if i != 0:
115 | vh = (i * 10)
116 | await elem.execute_script(f"""
117 | obj.style.cssText = `
118 | position: fixed;
119 | left: {vh}vw;
120 | top: {vh}vh;
121 | `;
122 | """)
123 |
124 | for value in values:
125 | await select(elem, value, async_input=async_input)
126 | trusted, value_got = await driver.execute_script("return [globalThis.trusted, globalThis.selected]",
127 | unique_context=True)
128 | with subtests.test():
129 | assert value == value_got
130 | with subtests.test():
131 | assert trusted
132 | with subtests.test():
133 | with pytest.raises(ValueError):
134 | elem = await driver.find_element(By.ID, "animals")
135 | await select(elem, "invalid", async_input=async_input, timeouts=0.001)
136 |
137 |
138 | @pytest.mark.skip("Wont fix")
139 | @pytest.mark.asyncio
140 | async def test_select(h_driver, subtests):
141 | await select_test(h_driver, subtests)
142 |
143 |
144 | @pytest.mark.asyncio
145 | async def test_select_headfull(driver, subtests):
146 | await select_test(driver, subtests, headfull=True)
147 |
--------------------------------------------------------------------------------
/tests/interaction/test_send_keys.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 | from selenium_driverless.types.target import KEY_MAPPING
5 | from selenium_driverless.types.webelement import WebElement
6 |
7 |
8 | @pytest.mark.asyncio
9 | async def test_assert_chars(h_driver, subtests):
10 | target = h_driver.current_target
11 | elem = await target.execute_script("""
12 | const elem = document.createElement("textarea")
13 | document.body.appendChild(elem)
14 | return elem
15 | """)
16 | assert isinstance(elem, WebElement)
17 | for key in KEY_MAPPING.keys():
18 | with subtests.test(key=key):
19 | await elem.send_keys(key, click_on=False)
20 | value = await elem.execute_script("return obj.value")
21 | if key == "\r":
22 | key = "\n"
23 | assert value == key
24 | await elem.execute_script("obj.value=''; obj.textContent=''")
25 | await asyncio.sleep(0.01)
26 |
--------------------------------------------------------------------------------
/tests/javascript/test_isolated_context.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.asyncio
5 | async def test_isolated_execution_context(h_driver):
6 | await h_driver.get('chrome://version')
7 | script = """
8 | const proxy = new Proxy(document.documentElement, {
9 | get(target, prop, receiver) {
10 | if(prop === "outerHTML"){
11 | console.log('detected access on "'+prop+'"', receiver)
12 | return "mocked value:)"
13 | }
14 | else{return Reflect.get(...arguments)}
15 | },
16 | });
17 | Object.defineProperty(document, "documentElement", {
18 | value: proxy
19 | })
20 | """
21 | await h_driver.execute_script(script, unique_context=False)
22 | src = await h_driver.execute_script("return document.documentElement.outerHTML")
23 | mocked = await h_driver.execute_script("return document.documentElement.outerHTML", unique_context=False)
24 | assert mocked == "mocked value:)"
25 | assert src != "mocked value:)"
26 |
--------------------------------------------------------------------------------
/tests/network/test_auth.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import uuid
3 | from urllib import parse
4 | from selenium_driverless.scripts.network_interceptor import NetworkInterceptor, InterceptedAuth
5 |
6 |
7 | async def on_auth(request: InterceptedAuth, user, _pass):
8 | await request.continue_auth(response="ProvideCredentials", username=user, password=_pass)
9 |
10 |
11 | def gen_url(test_server):
12 | user = uuid.uuid4().hex
13 | _pass = uuid.uuid4().hex
14 | resp = uuid.uuid4().hex
15 | url = test_server.url + "/auth_challenge?" + parse.urlencode(
16 | {"user": user, "pass": _pass, "resp": resp})
17 | return user, _pass, resp, url
18 |
19 |
20 | @pytest.mark.asyncio
21 | async def test_auth_extension(h_driver, subtests, test_server):
22 | user, _pass, resp, url = gen_url(test_server)
23 |
24 | with subtests.test():
25 | await h_driver.get(url, timeout=1)
26 | response = await h_driver.execute_script("return document.body.textContent")
27 | assert response != resp
28 |
29 | with subtests.test():
30 | await h_driver.set_auth(user, _pass, f"{test_server.host}:{test_server.port}")
31 | await h_driver.get(url, timeout=1)
32 | response = await h_driver.execute_script("return document.body.textContent")
33 | assert response == resp
34 |
35 |
36 | @pytest.mark.asyncio
37 | async def test_auth_interceptor(h_driver, subtests, test_server):
38 | user, _pass, resp, url = gen_url(test_server)
39 |
40 | with subtests.test():
41 | await h_driver.get(url, timeout=1)
42 | response = await h_driver.execute_script("return document.body.textContent")
43 | assert response != resp
44 |
45 | with subtests.test():
46 | async with NetworkInterceptor(h_driver, on_auth=lambda r: on_auth(r, user, _pass), intercept_auth=True):
47 | await h_driver.get(url, timeout=1)
48 | response = await h_driver.execute_script("return document.body.textContent")
49 | assert response == resp
50 |
51 | with subtests.test():
52 | # todo: fixme
53 | await h_driver.get(url, timeout=10)
54 | response = await h_driver.execute_script("return document.body.textContent")
55 | assert response == resp
56 |
--------------------------------------------------------------------------------
/tests/network/test_single_requests.py:
--------------------------------------------------------------------------------
1 | import json
2 | import pytest
3 | import uuid
4 | from urllib import parse
5 |
6 |
7 | @pytest.mark.asyncio
8 | async def test_fetch(h_driver, subtests, test_server):
9 | url = test_server.url + "/echo"
10 | target = h_driver.current_target
11 | await target.get(url)
12 | for body in [uuid.uuid4().hex, uuid.uuid4().bytes, {uuid.uuid4().hex: uuid.uuid4().hex}]:
13 | if isinstance(body, str):
14 | body_sent = body.encode("utf-8")
15 | elif isinstance(body, dict):
16 | body_sent = json.dumps(body).encode("utf-8")
17 | else:
18 | body_sent = body
19 |
20 | headers = {uuid.uuid4().hex: uuid.uuid4().hex}
21 | with subtests.test(body=body):
22 | res = await target.fetch(url, method="POST", referrer=url, headers=headers, body=body)
23 | assert res["status_code"] == 200
24 | for key, value in headers.items():
25 | assert res["headers"][key] == value
26 | assert res["body"] == body_sent
27 |
28 | headers = {uuid.uuid4().hex: uuid.uuid4().hex}
29 | with subtests.test(body=body):
30 | res = await target.fetch(url, method="GET", referrer=url, headers=headers)
31 | assert res["status_code"] == 200
32 | for key, value in headers.items():
33 | assert res["headers"][key] == value
34 |
35 |
36 | @pytest.mark.asyncio
37 | async def test_xhr(h_driver, subtests, test_server):
38 | url = test_server.url + "/echo"
39 | target = h_driver.current_target
40 | await target.get(url)
41 | for body in [uuid.uuid4().hex, uuid.uuid4().hex.encode("utf-8"), {uuid.uuid4().hex: uuid.uuid4().hex}]:
42 | if isinstance(body, bytes):
43 | body_sent = body.decode("utf-8")
44 | elif isinstance(body, dict):
45 | body_sent = json.dumps(body)
46 | else:
47 | body_sent = body
48 |
49 | headers = {uuid.uuid4().hex: uuid.uuid4().hex}
50 | with subtests.test(body=body):
51 | res = await target.xhr(url, method="POST", extra_headers=headers, body=body)
52 | assert res["status"] == 200
53 | for key, value in headers.items():
54 | assert res["responseHeaders"][key] == value
55 | assert res["responseText"] == body_sent
56 |
57 | headers = {uuid.uuid4().hex: uuid.uuid4().hex}
58 | with subtests.test(body=body):
59 | res = await target.xhr(url, method="GET", extra_headers=headers)
60 | assert res["status"] == 200
61 | for key, value in headers.items():
62 | assert res["responseHeaders"][key] == value
63 |
64 | with subtests.test(body=body):
65 | user = uuid.uuid4().hex
66 | _pass = uuid.uuid4().hex
67 | resp = uuid.uuid4().hex
68 | url = test_server.url + "/auth_challenge?" + parse.urlencode(
69 | {"user": user, "pass": _pass, "resp": resp})
70 |
71 | res = await target.xhr(url, method="GET", user=user, password=_pass)
72 | assert res["status"] == 200
73 | assert res["responseText"] == resp
74 |
--------------------------------------------------------------------------------
/tests/pages/test_cookies.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import datetime
3 | import json
4 | import uuid
5 | from urllib import parse
6 |
7 | import pytest
8 |
9 | from selenium_driverless.webdriver import Target
10 |
11 |
12 | async def get_del_cookie_test(target: Target, subtests, test_server):
13 | name = uuid.uuid4().hex
14 | value = uuid.uuid4().hex
15 | domain = "localhost"
16 | expires = datetime.datetime.utcnow() + datetime.timedelta(days=30)
17 |
18 | args = {
19 | "name": name,
20 | "value": value,
21 | "expires": expires.strftime("%a, %d %b %Y %H:%M:%S GMT"),
22 | "domain": domain
23 |
24 | }
25 | url = test_server.url + "/cookie_setter?" + parse.urlencode(args)
26 |
27 | cookies = await target.get_cookies()
28 | with subtests.test():
29 | assert len(cookies) == 0
30 |
31 | await target.get(url)
32 | cookies = await target.get_cookies()
33 | with subtests.test():
34 | assert len(cookies) == 1
35 | cookie = cookies[0]
36 |
37 | for key in ["name", "value", "domain", "path"]:
38 | with subtests.test(key=key):
39 | assert cookie["name"] == name
40 | with subtests.test():
41 | assert abs(expires - datetime.datetime.utcfromtimestamp(cookie["expires"])) < datetime.timedelta(seconds=1)
42 | # +- one second
43 | with subtests.test():
44 | await target.get(url)
45 | await target.delete_cookie(name, domain="localhost")
46 |
47 |
48 | async def assert_n_cookies(target1, target2, n1, n2, subtests):
49 | cookies1 = await target1.get_cookies()
50 | cookies2 = await target2.get_cookies()
51 | with subtests.test():
52 | assert len(cookies1) == n1
53 | with subtests.test():
54 | assert len(cookies2) == n2
55 |
56 |
57 | async def isolation_test(target1, target2, test_server, subtests):
58 | url = test_server.url + "/cookie_setter?name=test&value=test"
59 | await assert_n_cookies(target1, target2, 0, 0, subtests)
60 | await target1.get(url)
61 | await assert_n_cookies(target1, target2, 1, 0, subtests)
62 | await target1.delete_all_cookies()
63 | await target2.delete_all_cookies()
64 | await assert_n_cookies(target1, target2, 0, 0, subtests)
65 |
66 |
67 | @pytest.mark.asyncio
68 | async def test_get_del_cookie(h_driver, subtests, test_server):
69 | target = h_driver.current_target
70 | context = await h_driver.new_context()
71 | isolated = context.current_target
72 | with subtests.test():
73 | await get_del_cookie_test(target, subtests, test_server)
74 | with subtests.test():
75 | await get_del_cookie_test(isolated, subtests, test_server)
76 |
77 | await isolation_test(isolated, target, test_server, subtests)
78 | await isolation_test(target, isolated, test_server, subtests)
79 | context2 = await h_driver.new_context()
80 | await isolation_test(context2.current_target, isolated, test_server, subtests)
81 |
82 |
83 | async def get_echo_cookies(target, url) -> dict:
84 | loop = asyncio.get_event_loop()
85 | await target.get(url)
86 | resp = await target.fetch(url, credentials="include")
87 | return await loop.run_in_executor(None, lambda: json.loads(resp["body"].decode("utf-8")))
88 |
89 |
90 | @pytest.mark.asyncio
91 | async def test_set_cookie(h_driver, subtests, test_server):
92 | url = test_server.url + "/cookie_echo"
93 | target = h_driver.current_target
94 | expires = datetime.datetime.utcnow() + datetime.timedelta(days=30)
95 | cookie = {'name': uuid.uuid4().hex,
96 | 'value': uuid.uuid4().hex,
97 | 'domain': 'localhost',
98 | 'path': '/',
99 | 'expires': expires.timestamp(),
100 | 'httpOnly': False,
101 | 'secure': False,
102 | 'session': False,
103 | 'priority': 'High',
104 | 'sameParty': False,
105 | 'sourceScheme': 'NonSecure',
106 | 'sourcePort': test_server.port
107 | }
108 | cookies_received = await get_echo_cookies(target, url)
109 | with subtests.test():
110 | assert len(cookies_received.keys()) == 0
111 | await target.add_cookie(cookie)
112 | cookies_received = await get_echo_cookies(target, url)
113 | with subtests.test():
114 | assert cookies_received[cookie["name"]] == cookie["value"]
115 |
116 | get_cookie = (await target.get_cookies())[0]
117 | for key, value in cookie.items():
118 | value_got = get_cookie[key]
119 | with subtests.test(key=key, value=value, value_got=value_got):
120 | assert value == value_got
121 |
--------------------------------------------------------------------------------
/tests/server_for_testing.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import traceback
3 | import asyncio
4 | from selenium_driverless.utils.utils import random_port
5 | from aiohttp import web, BasicAuth, hdrs
6 | from aiohttp.web import middleware
7 | import json
8 |
9 |
10 | @middleware
11 | async def middleware(request, handler):
12 | try:
13 | resp = await handler(request)
14 | except Exception as e:
15 | if not isinstance(e, web.HTTPNotFound):
16 | traceback.print_exc()
17 | raise e
18 | return resp
19 |
20 |
21 | # noinspection PyMethodMayBeStatic
22 | class Server:
23 | port: int
24 | url: str
25 | host: str
26 | runner: web.AppRunner
27 | app: web.Application
28 | _started = False
29 | thread: threading.Thread
30 |
31 | def __init__(self, host: str = "localhost"):
32 | self.host = host
33 | self.app = web.Application(middlewares=[middleware])
34 | self.app.add_routes([
35 | web.get("/", self.root),
36 | web.get('/cookie_setter', self.cookie_setter),
37 | web.get("/cookie_echo", self.cookie_echo),
38 | web.get("/auth_challenge", self.auth_challenge),
39 | web.get("/echo", self.echo), web.post("/echo", self.echo)
40 | ])
41 |
42 | async def root(self, request: web.Request) -> web.Response:
43 | return web.Response(text="Hello World!", content_type="text/html")
44 |
45 | async def cookie_setter(self, request: web.Request) -> web.Response:
46 | resp = web.Response(text="Hello World!")
47 | resp.set_cookie(**request.query)
48 | return resp
49 |
50 | async def cookie_echo(self, request: web.Request) -> web.Response:
51 | resp = await asyncio.get_event_loop().run_in_executor(None, lambda: json.dumps(dict(**request.cookies)))
52 | return web.Response(text=resp, content_type="application/json")
53 |
54 | async def echo(self, request: web.Request) -> web.StreamResponse:
55 | response = web.StreamResponse(headers=request.headers)
56 | response.content_type = "text/html"
57 | await response.prepare(request)
58 | if request.can_read_body:
59 | async for data, _ in request.content.iter_chunks():
60 | await response.write(data)
61 | await response.write_eof()
62 | return response
63 |
64 | async def auth_challenge(self, request: web.Request) -> web.Response:
65 | auth_header = request.headers.get(hdrs.AUTHORIZATION)
66 | auth = None
67 | if auth_header:
68 | try:
69 | auth = BasicAuth.decode(auth_header=auth_header)
70 | except ValueError:
71 | pass
72 | if auth is None or auth.login != request.query["user"] or auth.login == request.query["pass"]:
73 | return web.Response(
74 | body=b'',
75 | status=401,
76 | reason='UNAUTHORIZED',
77 | headers={
78 | hdrs.WWW_AUTHENTICATE: f'Basic realm="Hello"',
79 | hdrs.CONTENT_TYPE: 'text/html; charset=utf-8',
80 | hdrs.CONNECTION: 'keep-alive',
81 | },
82 | )
83 | return web.Response(text=request.query["resp"])
84 |
85 | def __enter__(self):
86 | if not self._started:
87 | self.port = random_port()
88 | self.url = f"http://{self.host}:{self.port}"
89 | self.thread = threading.Thread(target=lambda: web.run_app(self.app, host=self.host, port=self.port),
90 | daemon=True)
91 | self.thread.start()
92 | return self
93 |
94 | def __exit__(self, exc_type, exc_val, exc_tb):
95 | asyncio.ensure_future(self.app.shutdown())
96 | self.thread.join(timeout=5)
97 |
--------------------------------------------------------------------------------
/tests/sync/test_sync_selenium_detector.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from selenium_driverless.types.by import By
4 |
5 |
6 | @pytest.mark.skip_offline
7 | def test_sync_selenium_detector(sync_h_driver):
8 | sync_h_driver.get('https://hmaker.github.io/selenium-detector/')
9 | elem = sync_h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-token")
10 |
11 | elem.write(sync_h_driver.execute_script('return window.token', unique_context=False))
12 | elem2 = sync_h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-asynctoken")
13 | async_token = sync_h_driver.eval_async('return await window.getAsyncToken()', unique_context=False)
14 | elem2.write(async_token)
15 | elem3 = sync_h_driver.find_element(By.CSS_SELECTOR, "#chromedriver-test")
16 | sync_h_driver.sleep(0.2)
17 | elem3.click()
18 | passed = sync_h_driver.find_element(By.XPATH, '//*[@id="chromedriver-test-container"]/span')
19 | text = passed.text
20 | assert text == "Passed!"
21 |
--------------------------------------------------------------------------------
/tests/test_other.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import pytest
3 |
4 |
5 | @pytest.mark.asyncio
6 | async def test_prompt(h_driver):
7 | await h_driver.get("chrome://version")
8 | keys = "Hello!"
9 | fut = asyncio.ensure_future(h_driver.execute_script("return prompt('hello?')", timeout=100))
10 | await asyncio.sleep(0.5)
11 | alert = await h_driver.current_target.get_alert(timeout=5)
12 | await alert.send_keys(keys)
13 | res = await fut
14 | assert res == keys
15 |
--------------------------------------------------------------------------------