├── .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 | ![img.png](assets/heatmap.png) 8 | 9 | `bias_a = 0.7` 10 | ![img.png](assets/heatmap_biased.png) 11 | 12 | 13 | ### mouse path 14 | 15 | #### generated example 16 | ![img.png](assets/mousemove_events_gen.png) 17 | ![img.png](assets/mouse_path_gen.png) 18 | 19 | #### test in Browser based on generated path 20 | ![img.png](assets/mousemove_events_test_sample_based.png) 21 | ![img.png](assets/mousemove_events_test_samples_based.png) 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 | ![img.png](assets/real_mouse_path.png) 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 | ![img.png](assets/events_mousepad.png) 40 | - with mouse 41 | ![img.png](assets/events_mouse.png) 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 | 58 | 59 |
63 | 64 |
65 |
66 |
67 |
    68 |
  • 69 | 70 |
  • 71 |
  • 72 |
73 |
74 |
75 | 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 | 58 | 59 |
63 | 64 |
65 |
66 |
67 |
    68 |
  • 69 | 70 | 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 | 58 | 59 |
63 | 64 |
65 |
66 |
67 |
    68 |
  • 69 | 70 | 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 | '" 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 | 60 | 61 |
65 | 66 |
67 |
68 |
69 |
    70 |
  • 71 | 72 |
  • 73 |
  • 74 |
75 |
76 |
77 |
78 |
79 | 80 | 81 |

Python Module Index

82 | 83 |
84 | s 85 |
86 | 87 | 88 | 89 | 91 | 92 | 94 | 97 | 98 | 99 | 102 |
 
90 | s
95 | selenium_driverless 96 |
    100 | selenium_driverless.scripts.network_interceptor 101 |
103 | 104 | 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |

© Copyright 2024, Aurin Aegerter (aka Steve, kaliiiiiiiiii).

113 |
114 | 115 | Built with Sphinx using a 116 | theme 117 | provided by Read the Docs. 118 | 119 | 120 |
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 | 61 | 62 |
66 | 67 |
68 |
69 |
70 |
    71 |
  • 72 | 73 |
  • 74 |
  • 75 |
76 |
77 |
78 |
79 |
80 | 81 | 88 | 89 | 90 |
91 | 92 |
93 | 94 |
95 |
96 |
97 | 98 |
99 | 100 |
101 |

© Copyright 2024, Aurin Aegerter (aka Steve, kaliiiiiiiiii).

102 |
103 | 104 | Built with Sphinx using a 105 | theme 106 | provided by Read the Docs. 107 | 108 | 109 |
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 | 61 | 62 |
66 | 67 |
68 |
69 |
70 |
    71 |
  • 72 | 73 |
  • 74 |
  • 75 |
76 |
77 |
78 |
79 |
80 | 81 | 88 | 89 | 90 |
91 | 92 |
93 | 94 |
95 |
96 |
97 | 98 |
99 | 100 |
101 |

© Copyright 2024, Aurin Aegerter (aka Steve, kaliiiiiiiiii).

102 |
103 | 104 | Built with Sphinx using a 105 | theme 106 | provided by Read the Docs. 107 | 108 | 109 |
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 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 | 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 | --------------------------------------------------------------------------------