{"id":19725138,"url":"https://github.com/aresjef/aselenium","last_synced_at":"2025-02-27T18:52:05.961Z","repository":{"id":207882824,"uuid":"720317556","full_name":"AresJef/Aselenium","owner":"AresJef","description":"Asyncronous implementation of web browser automation for Python.","archived":false,"fork":false,"pushed_at":"2025-02-17T03:31:54.000Z","size":4335,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-17T03:46:32.628Z","etag":null,"topics":["arsenic","asyncronous","automation","python","selenium"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AresJef.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-18T05:23:01.000Z","updated_at":"2025-02-17T03:29:56.000Z","dependencies_parsed_at":"2023-12-08T08:24:21.572Z","dependency_job_id":"b3bd6eb5-ab20-4dd0-bb48-1c461c8a849f","html_url":"https://github.com/AresJef/Aselenium","commit_stats":null,"previous_names":["aresjef/aselenium"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AresJef%2FAselenium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AresJef%2FAselenium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AresJef%2FAselenium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AresJef%2FAselenium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AresJef","download_url":"https://codeload.github.com/AresJef/Aselenium/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241045805,"owners_count":19899729,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arsenic","asyncronous","automation","python","selenium"],"created_at":"2024-11-11T23:28:26.732Z","updated_at":"2025-02-27T18:52:05.936Z","avatar_url":"https://github.com/AresJef.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Asyncronous implementation of web browser automation for Python.\n\nCreated to be used in a project, this package is published to github for ease of management and installation across different modules.\n\n### Features\n- **Asynchronous Execution**: Aselenium is inspired by the [HENNGE/arsenic](https://github.com/HENNGE/arsenic) project, enabling multiple browser sessions to run concurrently. This allows Python to execute other async tasks without being blocked by time-consuming browser actions.\n\n- **Webdriver Management**: Aselenium integrates the webdriver installation and management, taking inspiration from the [SergeyPirogov/webdriver_manager](https://github.com/SergeyPirogov/webdriver_manager) project. It provides a simple interface for user to install the proper/desired webdriver for different browsers cross platforms.\n\n- **Browser Support**: Aselenium supports popular browsers such as Chrome, Chromium, Edge, Firefox, and Safari.\n\n### Installation\nInstall from `PyPi`\n``` bash\npip install aselenium\n```\n\nInstall from `github`\n``` bash\npip install git+https://github.com/AresJef/Aselenium.git\n```\n\n### Compatibility\nOnly support for python 3.10 and above.\n\n### Chrome Automation\n``` python\nfrom aselenium import Chrome\ndriver = Chrome(\n    # optional: the directory to store the webdrivers.\n    directory=\"/path/to/driver/cache/directory\"\n    # optional: the maximum amount of webdrivers (and CTF browsers) to maintain.\n    max_cache_size=10\n)\n\n# Set options\ndriver.options.add_arguments('--headless', '--disable-gpu')\n...\n\n# Acquire a chrome session\nasync with driver.acquire(\"build\", \"dev\") as session\n    # explain: install webdriver that has the same major \u0026 build\n    # version as the Chrome [dev] browser installed in the system,\n    # and start a new session with the dev browser.\n    await session.load(\"https://www.google.com\")\n    # . do some automated tasks\n    ...\n\n# Acquire a CFT [Chrome for Testing] session\nasync with driver.acquire(\"119.0.6045\", \"cft\") as session:\n    # explain: install both the webdriver and CFT browser with the \n    # same build version '119.0.6045', and start a new session with \n    # the CFT browser.\n    await session.load(\"https://www.google.com\")\n    # . do some automated tasks\n    ...\n```\n\n### Edge Automation\n``` python\nfrom aselenium import Edge\ndriver = Edge()\n\n# Set options\ndriver.options.add_arguments('--headless', '--disable-gpu')\n...\n\n# Acquire an edge session\nasync with driver.acquire(\"build\", \"stable\") as session:\n    # explain: install webdriver that has the same major \u0026 build\n    # version as the Edge [stable] browser installed in the system,\n    # and start a new session with the stable Edge browser.\n    await session.load(\"https://www.google.com\")\n    # . do some automated tasks\n    ...\n```\n\n### Firefox Automation\n``` python\nfrom aselenium import Firefox\ndriver = Firefox()\n\n# Set options\ndriver.options.add_arguments('--headless', '--disable-gpu')\n...\n\n# Acquire a firefox session\nasync with driver.acquire(\"latest\") as session:\n    # explain: install the latest geckodriver available at\n    # [Mozilla Github] repository that is compatible with\n    # the installed Firefox browser, and start a new session.\n    await session.load(\"https://www.google.com\")\n    # . do some automated tasks\n    ...\n```\n\n### Options configuration\nSetting options for aselenium drivers does not require to import an Options class. Instead, the options can be accessed directly from the driver instance. The following example shows how to set options for Chrome, but the same method (if applicable) can also apply to other browsers.\n\n``` python\nfrom aselenium import Chrome, Proxy\ndriver = Chrome()\n# . accept Insecure Certs\ndriver.options.accept_insecure_certs = True\n# . page Load Strategy\ndriver.options.page_load_strategy = \"eager\"\n# . proxy\nproxy = Proxy(\n    http_proxy=\"http://127.0.0.1:7890\",\n    https_proxy=\"http://127.0.0.1:7890\",\n    socks_proxy=\"socks5://127.0.0.1:7890\",\n)\ndriver.options.proxy = proxy\n# . timeout\ndriver.options.set_timeouts(implicit=5, pageLoad=10)\n# . strict file interactability\ndriver.options.strict_file_interactability = True\n# . prompt behavior\ndriver.options.unhandled_prompt_behavior = \"dismiss\"\n# . arguments\ndriver.options.add_arguments(\"--disable-gpu\", \"--disable-dev-shm-usage\")\n# . preferences [Keywork Arguments]\ndriver.options.set_preferences(\n    **{\n        \"download.default_directory\": \"/path/to/download/directory\",\n        \"download.prompt_for_download\": False,\n        \"download.directory_upgrade\": True,\n        \"safebrowsing.enabled\": True\n    }\n)\n# . experimental options [Keywork Arguments]\ndriver.options.add_experimental_options(\n    excludeSwitches=[\"enable-automation\", \"enable-logging\"]\n)\n...\n```\n\n### Profile configuration\nThe options of aselenium drivers provides the 'set_profile' method to configure the profile of the browser. By using this method, a cloned temporary profile will be created based on the specified profile directory, leaving the original profile untouched. When the driver is no longer in use, the temporary profile will be deleted automatically. The following example shows how to set profile for Chrome, but the same method (if applicable) can also apply to other browsers.\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\ndriver.options.set_profile(\n    # . the default profile directory for Chrome on MacOS\n    directory=\"~/Library/Application Support/Google/Chrome\", \n    profile=\"Default\",\n)\n```\n\n### Navigation\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    # . load a url\n    await session.load(\"https://www.google.com\")\n    # . explicitly wait for page loading: url\n    res = await session.wait_until_url(\"contains\", \"google\", timeout=10)  # True / False\n    # . explicitly wait for page loading: title\n    res = await session.wait_until_title(\"startswith\", \"Google\", timeout=10)  # True / False\n    # . backward\n    await session.backward()\n    # . forward\n    await session.forward()\n    # . refresh\n    await session.refresh()\n```\n\n### Page Information\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    await session.load(\"https://www.google.com\")\n    # . access page url [property]\n    url = await session.url  # https://www.google.com\n    # . access page title [property]\n    title = await session.title  # Google\n    # . access page viewport [property]\n    viewport = await session.viewport  # \u003cViewport (width=1200, height=776, x=0, y=2143)\u003e\n    # . access page width [property]\n    width = await session.width  # 1200\n    # . access page height [property]\n    height = await session.height  # 776\n    # . access page source [property]\n    source = await session.source  # \u003c!DOCTYPE html\u003e\u003chtml itemscope=\"\" itemtype=\"http://...\n    # . take screenshot\n    data = await session.screenshot()  # b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\...\n    # . save screenshot\n    await session.save_screenshot(\"~/path/to/screenshot.png\")  # True / False\n    # . print page\n    data = await session.print_page()  # b'%PDF-1.4\\n%\\xe2\\xe3\\xcf\\xd3\\n1...\n    # . save page\n    await session.save_page(\"~/path/to/page.pdf\")  # True / False\n```\n\n### Session Timeouts\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    # . access timeouts of the session [property]\n    timeouts = await options.timeouts\n    # \u003cTimeouts (implicity=0, pageLoad=300000, script=30000, unit='ms')\u003e\n\n    # . set timeouts of the session\n    timeouts = await session.set_timeouts(implicit=0.1, pageLoad=30, script=3)\n    # \u003cTimeouts (implicity=100, pageLoad=30000, script=3000, unit='ms')\u003e\n\n    # . reset timeouts of the session\n    timeouts = await session.reset_timeouts()\n    # \u003cTimeouts (implicity=0, pageLoad=300000, script=30000, unit='ms')\u003e\n```\n\n### Session Windows\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    await session.load(\"https://www.google.com\")\n    # . access the active window [property]\n    win = await session.active_window\n    # \u003cWindow (name='default', handle='CCEF49C484842CFE1AB855ECCA164858')\u003e\n\n    # . rename a window\n    win = await session.rename_window(\"default\", \"google\")\n    # \u003cWindow (name='google', handle='CCEF49C484842CFE1AB855ECCA164858')\u003e\n\n    # . open a new window\n    win = await session.new_window(\"baidu\", \"tab\")\n    # \u003cWindow (name='baidu', handle='B89293FA79B6389AF1657B972FBD6B26')\u003e\n    await session.load(\"https://www.baidu.com\")\n\n    # . access all opened windows [property]\n    wins = await session.windows\n    # [\n    #    \u003cWindow (name='google', handle='CCEF49C484842CFE1AB855ECCA164858')\u003e\n    #    \u003cWindow (name='baidu', handle='B89293FA79B6389AF1657B972FBD6B26')\u003e\n    # ]\n\n    # . close current window\n    await session.close_window(switch_to=\"google\")\n    # \u003cWindow (name='google', handle='CCEF49C484842CFE1AB855ECCA164858')\u003e\n\n    # . access window rect [property]\n    rect = await session.window_rect\n    # \u003cWindowRect (width=1200, height=900, x=22, y=60)\u003e\n\n    # . set window rect\n    rect = await session.set_window_rect(800, 500)\n    # \u003cWindowRect (width=800, height=500, x=22, y=60)\u003e\n\n    # . maximize window\n    await session.maximize_window()\n\n    # . minimize window\n    await session.minimize_window()\n\n    # . fullscreen window\n    await session.fullscreen_window()\n```\n\n### Active Window Cookies\n``` python\nfrom aselenium import Chrome, Cookie\ndriver = Chrome()\nasync with driver.acquire() as session:\n    await session.load(\"https://www.baidu.com\")\n\n    # . access all cookies [property]\n    cookies = await session.cookies\n    # [\n    #    \u003cCookie (name='ZFY', data={'domain': '.baidu.com', 'expiry': 1720493275, ...})\u003e\n    #    \u003cCookie (name='ZFK', data={'domain': '.baidu.com', 'expiry': 1720493275, ...})\u003e\n    #    ...\n    # ]\n\n    # . get a cookie\n    cookie = await session.get_cookie(\"ZFY\")\n    # \u003cCookie (name='ZFY', data={'domain': '.baidu.com', 'expiry': 1720493275, ...})\u003e\n\n    # . add a cookie\n    await session.add_cookie({\"name\": \"test_cookie2\", \"value\": \"123456\", \"domain\": \".baidu.com\"})\n    # // or //\n    test_cookie2 = Cookie(name=\"test_cookie1\", value=\"123456\", domain=\".baidu.com\")\n    await session.add_cookie(test_cookie2)\n\n    # . delete a cookie\n    await session.delete_cookie(\"test_cookie1\")\n    # // or //\n    await session.delete_cookie(test_cookie2)\n```\n\n### JavaScript Execution\nNotice that some of the following methods are not async functions, therefore should not be awaited.\n\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    await session.load(\"https://www.google.com\")\n    # . execute a raw javascript\n    res = await session.execute_script(\"return document.title;\")  # Google\n\n    # . cache a javascript\n    js = session.cache_script(\"get_title\", \"return document.title;\")\n    # \u003cJavaScript (name='get_title', script='return document.title;', args=[])\u003e\n\n    # . access all cached javascripts\n    scripts = session.scripts\n    # [\u003cJavaScript (name='get_title', script='return document.title;', args=[])\u003e]\n\n    # . get a cached javascript\n    js = session.get_script(\"get_title\")\n    # \u003cJavaScript (name='get_title', script='return document.title;', args=[])\u003e\n\n    # . execute cached javascript\n    res = await session.execute_script(js)  # Google\n    # // or //\n    res = await session.execute_script(\"get_title\")  # Google\n\n    # . rename cached javascript\n    js = session.rename_script(\"get_title\", \"access_title\")\n    # // or //\n    js = session.rename_script(js, \"access_title\")\n    # \u003cJavaScript (name='access_title', script='return document.title;', args=[])\u003e\n\n    # . remove cached javascript\n    session.remove_script(js)\n    # // or //\n    session.remove_script(\"access_title\")\n```\n\n### Element Interaction\n``` python\nfrom aselenium import Chrome\ndriver = Chrome()\nasync with driver.acquire() as session:\n    await session.load(\"https://www.baidu.com\")\n    # . find element\n    element = await session.find_element(\"#kw\", by=\"css\")\n    # \u003cElement (id='289DEC2B8885F15A2BDD2E92AC0404F3_element_1', session='1e78...', service='http://...')\u003e\n\n    # . find elements\n    elements = await session.find_elements(\".s_ipt\", by=\"css\")\n    # [\n    #       \u003cElement (id='289DEC2B8885F15A2BDD2E92AC0404F3_element_2', session='1e78...', service='http://...')\u003e\n    #       \u003cElement (id='289DEC2B8885F15A2BDD2E92AC0404F3_element_3', session='1e78...', service='http://...')\u003e\n    #       ...\n    # ]\n\n    # . find the 1st located element among selectors\n    element = await session.find_1st_element(\"#kw\", \".s_ipt\", by=\"css\")\n    # \u003cElement (id='289DEC2B8885F15A2BDD2E92AC0404F3_element_1', session='1e78...', service='http://...')\u003e\n    \n    # . explicit wait for element\n    res = await session.wait_until_element(\"visible\", \"#kw\", by=\"css\", timeout=10)  # True\n    res = await session.wait_until_element(\"gone\", \"#kw\", by=\"css\", timeout=10) # False\n\n    # . explicit wait for elements\n    res = await session.wait_until_elements(\n        \"exist\", \"#kw\", \".s_ipt\", by=\"css\", all_=True, timeout=10\n    )  # True\n    res = await session.wait_until_elements(\n        \"selected\", \"#kw\", \".s_ipt\", by=\"css\", all_=True, timeout=10\n    )  # False\n\n    # . access element properties\n    exists = await element.exists  # True\n    visible = await element.visible  # True\n    viewable = await element.viewable  # True\n    enabled = await element.enabled  # True\n    selected = await element.selected  # False\n    tag = await element.tag  # input\n    text = await element.text  # None\n    rect = await element.rect  # \u003cElementRect (width=500, height=22, x=522, y=217)\u003e\n    aria_role = await element.aria_role  # None\n    aria_label = await element.aria_label  # None\n    ...\n\n    # . element interaction\n    await element.click()\n    await element.send(\"Hello World!\")\n    await element.clear()\n    await element.upload(\"~/path/to/file.png\")\n    await element.scroll_into_view()\n    ...\n```\n\n### Actions Chain\nActions chain allows the browser to perform a series of low-level interactions such as mouse movements, key presses, and wheel scrolls.\n\n- For Chromium-based browsers like Chrome, Chromium, and Edge, the actions chain execution passes most tests, including 'drag_and_drop'.\n- For Firefox, the actions chain execution fails the 'drag_and_drop' test. Additionally, user must explicitly block the code execution until all actions are completed. This is because geckodriver returns a response immediately after receiving the actions command (where chromedriver only returns a response when all actions are completed). To do so, user can choose to use the 'explicit_wait' argument in the 'perform()' method to wait the specified amount of seconds, or implement a custom logic right after the actions chain execution.\n``` python\nfrom aselenium import Chrome, KeyboardKeys\ndriver = Chrome()\nasync with driver.acquire() as session:\n    # Actions chain\n    await session.load(\"https://www.baidu.com\")\n    search_button = await session.find_element(\"#su\", by=\"css\")\n    (\n        await session.actions()\n        # . move mouse to the element\n        .move_to(element=search_button)\n        # . click the element\n        .click(pause=1)\n        # . send text\n        .send_keys(\"Hello World!\", pause=1)\n        # . select all (control + a)\n        .send_key_combo(KeyboardKeys.CONTROL, \"a\", pause=1)\n        # . cut (control + x)\n        .send_key_combo(KeyboardKeys.CONTROL, \"x\", pause=1)\n        # . paste (control + v)\n        .send_key_combo(KeyboardKeys.CONTROL, \"v\", pause=1)\n        # . enter\n        .send_keys(KeyboardKeys.ENTER, pause=1)\n        # . scroll wheel\n        .scroll_by(y=500, pause=1)\n        # . execute the actions chain\n        .perform()  \n        # For Firefox, specify an explicit wait time to wait for the \n        # actions to complete: `.perform(explicit_wait=20)`.\n    )\n\n    # Drag and drop\n    await session.load(\"https://www.w3schools.com/html/html5_draganddrop.asp\")\n    l_element = await session.find_element(\"#div1\", by=\"css\")\n    r_element = await session.find_element(\"#div2\", by=\"css\")\n    (\n        await session.actions()\n        # . drag and drop: left -\u003e right\n        .drag_and_drop(drag=l_element, drop=r_element, pause=1)\n        # . drag and drop: right -\u003e left\n        .drag_and_drop(drag=r_element, drop=l_element, pause=1)\n        # . execute the actions chain\n        .perform()\n        # For Firefox, `drag_and_drop` is not supported.\n    )\n```\n\n### Known Issues\n- For Linux users, browsers installed through 'snap store' will failed due to the 'snap' sandbox. Snap version of the browsers lack the permissions to create and access a temporary profile out side of snap, which is required by the webdriver to start the browser. The most straight forward solution is to uninstall the snap version and re-install the browser through the official website.\n- For Firefox, the actions chain does not work quite as expected. If actions chain is important for the project, consider using Chromium-based browsers instead.\n- Safari is supported by aselenium, but limited to some basic functionalities, and not recommended to use for critical automations. The webdriver for Safari yeilds many unexpected errors and sometimes even crashes the browser and program.\n\n### Acknowledgements\naselenium is based on several open-source repositories.\n- [aiohttp](https://github.com/aio-libs/aiohttp)\n- [psutil](https://github.com/giampaolo/psutil)\n- [orjson](https://github.com/ijl/orjson)\n\naselenium is inspired from and makes modifications of the following open-source repositories:\n- [arsenic](https://github.com/HENNGE/arsenic)\n- [selenium](https://github.com/SeleniumHQ/selenium)\n- [webdriver-manager](https://github.com/SergeyPirogov/webdriver_manager)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faresjef%2Faselenium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faresjef%2Faselenium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faresjef%2Faselenium/lists"}