{ "info": { "author": "ClericPy", "author_email": "clericpy@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7" ], "description": "# [ichrome - v0.2.0](https://github.com/ClericPy/ichrome)\n\n> A toolkit for using chrome browser with the [Chrome Devtools Protocol(CDP)](https://chromedevtools.github.io/devtools-protocol/), support python3.7+.\n\n## Install\n\n> pip install ichrome -U\n\n## Why?\n\n- pyppeteer / selenium is awesome, but I don't need so much\n - spelling of pyppeteer is hard to remember.\n - selenium is slow.\n- async communication with Chrome remote debug port, stable choice. [Recommended]\n- sync way to test CDP, which is not recommended for complex production environments. [Deprecated]\n\n\n## Features\n\n- Chrome process daemon\n- Connect to existing chrome debug port\n- Operations on Tabs\n\n## Examples\n\n\n### Chrome daemon\n\n```python\nfrom ichrome import ChromeDaemon, Chrome\n\n\ndef main():\n with ChromeDaemon() as chromed:\n # run_forever means auto_restart\n chromed.run_forever(0)\n chrome = Chrome()\n tab = chrome.new_tab(url=\"https://pypi.org\")\n tab.wait_loading(3)\n tab.js('alert(\"test ok\")')\n tab.close()\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n### Command Line Usage\n\n```\n\u03bb python3 -m ichrome -s 9222\n2018-11-27 23:01:59 DEBUG [ichrome] base.py(329): kill chrome.exe --remote-debugging-port=9222\n2018-11-27 23:02:00 DEBUG [ichrome] base.py(329): kill chrome.exe --remote-debugging-port=9222\n\n\u03bb python3 -m ichrome -p 9222 --start_url \"http://bing.com\" --disable_image\n2018-11-27 23:03:57 INFO [ichrome] __main__.py(69): ChromeDaemon cmd args: {'daemon': True, 'block': True, 'chrome_path': '', 'host': 'localhost', 'port': 9222, 'headless': False, 'user_agent': '', 'proxy': '', 'user_data_dir': None, 'disable_image': True, 'start_url': 'http://bing.com', 'extra_config': '', 'max_deaths': 2, 'timeout': 2}\n```\n\n### [Async] Operation with asyncio\n\n```python\nimport asyncio\nimport os\nimport sys\n\n# use local ichrome module\nsys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))\nos.chdir(\"..\") # for reuse exiting user data dir\n\n\nasync def test_examples():\n from ichrome.async_utils import Chrome, logger, Tab, Tag\n from ichrome import ChromeDaemon\n import threading\n\n logger.setLevel('DEBUG')\n # Tab._log_all_recv = True\n port = 9222\n\n def async_run_chromed():\n\n def chrome_daemon():\n with ChromeDaemon(\n host=\"127.0.0.1\", port=port, max_deaths=1) as chromed:\n chromed.run_forever()\n\n threading.Thread(target=chrome_daemon).start()\n\n async_run_chromed()\n # ===================== Chrome Test Cases =====================\n chrome = Chrome()\n assert str(chrome) == ''\n assert chrome.server == 'http://127.0.0.1:9222'\n try:\n await chrome.version\n except AttributeError as e:\n assert str(\n e\n ) == 'Chrome has not connected. `await chrome.connect()` before request.'\n connected = await chrome.connect()\n assert connected is True\n version = await chrome.version\n assert isinstance(version, dict) and 'Browser' in version\n ok = await chrome.check()\n assert ok is True\n ok = await chrome.ok\n assert ok is True\n resp = await chrome.get_server('json')\n assert isinstance(resp.json(), list)\n tabs1 = await chrome.get_tabs()\n tabs2 = await chrome.tabs\n assert tabs1 == tabs2\n tab0 = tabs1[0]\n tab1 = await chrome.new_tab()\n assert isinstance(tab1, Tab)\n await asyncio.sleep(1)\n await chrome.activate_tab(tab0)\n async with chrome.connect_tabs([tab0, tab1]):\n assert (await tab0.current_url) == 'about:blank'\n assert (await tab1.current_url) == 'about:blank'\n async with chrome.connect_tabs(tab0):\n assert await tab0.current_url == 'about:blank'\n await chrome.close_tab(tab1)\n # ===================== Tab Test Cases =====================\n tab = await chrome.new_tab()\n assert tab.ws is None\n async with tab():\n assert tab.ws\n assert tab.ws is None\n async with tab.connect():\n assert tab.status == 'connected'\n assert tab.msg_id == tab.msg_id - 1\n assert await tab.refresh_tab_info()\n\n # watch the tabs switch\n await tab.activate_tab()\n await asyncio.sleep(.5)\n await tab0.activate_tab()\n await asyncio.sleep(.5)\n await tab.activate_tab()\n\n assert await tab.send('Network.enable') == {'id': 3, 'result': {}}\n await tab.clear_browser_cookies()\n assert len(await tab.get_cookies(urls='http://python.org')) == 0\n assert await tab.set_cookie(\n 'test', 'test_value', url='http://python.org')\n assert await tab.set_cookie(\n 'test2', 'test_value', url='http://python.org')\n assert len(await tab.get_cookies(urls='http://python.org')) == 2\n assert await tab.delete_cookies('test', url='http://python.org')\n assert len(await tab.get_cookies(urls='http://python.org')) == 1\n # get all Browser cookies\n assert len(await tab.get_all_cookies()) > 0\n # disable Network\n assert await tab.disable('Network')\n # set new url for this tab, timeout will stop loading\n assert await tab.set_url('http://python.org', timeout=3)\n # reload the page\n assert await tab.reload(timeout=3)\n # here should be press OK by human in 10 secs, get the returned result\n js_result = await tab.js('document.title', timeout=10)\n # {'id': 18, 'result': {'result': {'type': 'string', 'value': 'Welcome to Python.org'}}}\n assert 'result' in js_result\n # inject JS timeout return None\n assert (await tab.js('alert()', timeout=0.1)) is None\n # close the alert dialog\n await tab.enable('Page')\n await tab.send('Page.handleJavaScriptDialog', accept=True)\n # querySelectorAll with JS, return list of Tag object\n tag_list = await tab.querySelectorAll('#id-search-field')\n assert tag_list[0].tagName == 'input'\n # querySelectorAll with JS, index arg is Not None, return Tag or None\n one_tag = await tab.querySelectorAll('#id-search-field', index=0)\n assert isinstance(one_tag, Tag)\n # inject js url: vue.js\n # get window.Vue variable before injecting\n vue_obj = await tab.js('window.Vue')\n # {'id': 22, 'result': {'result': {'type': 'undefined'}}}\n assert 'undefined' in str(vue_obj)\n assert await tab.inject_js_url(\n 'https://cdn.staticfile.org/vue/2.6.10/vue.min.js', timeout=5)\n vue_obj = await tab.js('window.Vue')\n # {'id': 23, 'result': {'result': {'type': 'function', 'className': 'Function', 'description': 'function wn(e){this._init(e)}', 'objectId': '{\"injectedScriptId\":1,\"id\":1}'}}}\n assert 'Function' in str(vue_obj)\n\n # wait_response by filter_function\n # {'method': 'Network.responseReceived', 'params': {'requestId': '1000003000.69', 'loaderId': 'D7814CD633EDF3E699523AF0C4E9DB2C', 'timestamp': 207483.974238, 'type': 'Script', 'response': {'url': 'https://www.python.org/static/js/libs/masonry.pkgd.min.js', 'status': 200, 'statusText': '', 'headers': {'date': 'Sat, 05 Oct 2019 08:18:34 GMT', 'via': '1.1 vegur, 1.1 varnish, 1.1 varnish', 'last-modified': 'Tue, 24 Sep 2019 18:31:03 GMT', 'server': 'nginx', 'age': '290358', 'etag': '\"5d8a60e7-6643\"', 'x-served-by': 'cache-iad2137-IAD, cache-tyo19928-TYO', 'x-cache': 'HIT, HIT', 'content-type': 'application/x-javascript', 'status': '200', 'cache-control': 'max-age=604800, public', 'accept-ranges': 'bytes', 'x-timer': 'S1570263515.866582,VS0,VE0', 'content-length': '26179', 'x-cache-hits': '1, 170'}, 'mimeType': 'application/x-javascript', 'connectionReused': False, 'connectionId': 0, 'remoteIPAddress': '151.101.108.223', 'remotePort': 443, 'fromDiskCache': True, 'fromServiceWorker': False, 'fromPrefetchCache': False, 'encodedDataLength': 0, 'timing': {'requestTime': 207482.696803, 'proxyStart': -1, 'proxyEnd': -1, 'dnsStart': -1, 'dnsEnd': -1, 'connectStart': -1, 'connectEnd': -1, 'sslStart': -1, 'sslEnd': -1, 'workerStart': -1, 'workerReady': -1, 'sendStart': 0.079, 'sendEnd': 0.079, 'pushStart': 0, 'pushEnd': 0, 'receiveHeadersEnd': 0.836}, 'protocol': 'h2', 'securityState': 'unknown'}, 'frameId': 'A2971702DE69F008914F18EAE6514DD5'}}\n task = asyncio.ensure_future(\n tab.wait_response(\n filter_function=\n lambda r: print(r['params']['response']['url']) or r['params']['response']['url'] == 'https://www.python.org/downloads/',\n timeout=10),\n loop=tab.loop)\n\n # click download link, without wait_loading.\n await tab.click('#downloads > a')\n # {'method': 'Network.responseReceived', 'params': {'requestId': '2FAFC4FC410A6DEDE88553B1836C530B', 'loaderId': '2FAFC4FC410A6DEDE88553B1836C530B', 'timestamp': 212239.182469, 'type': 'Document', 'response': {'url': 'https://www.python.org/downloads/', 'status': 200, 'statusText': '', 'headers': {'status': '200', 'server': 'nginx', 'content-type': 'text/html; charset=utf-8', 'x-frame-options': 'DENY', 'cache-control': 'max-age=604800, public', 'via': '1.1 vegur\\n1.1 varnish\\n1.1 varnish', 'accept-ranges': 'bytes', 'date': 'Sat, 05 Oct 2019 10:51:48 GMT', 'age': '282488', 'x-served-by': 'cache-iad2139-IAD, cache-hnd18720-HND', 'x-cache': 'MISS, HIT', 'x-cache-hits': '0, 119', 'x-timer': 'S1570272708.444646,VS0,VE0', 'content-length': '113779'}, 'mimeType': 'text/html', 'connectionReused': False, 'connectionId': 0, 'remoteIPAddress': '123.23.54.43', 'remotePort': 443, 'fromDiskCache': True, 'fromServiceWorker': False, 'fromPrefetchCache': False, 'encodedDataLength': 0, 'timing': {'requestTime': 212239.179388, 'proxyStart': -1, 'proxyEnd': -1, 'dnsStart': -1, 'dnsEnd': -1, 'connectStart': -1, 'connectEnd': -1, 'sslStart': -1, 'sslEnd': -1, 'workerStart': -1, 'workerReady': -1, 'sendStart': 0.392, 'sendEnd': 0.392, 'pushStart': 0, 'pushEnd': 0, 'receiveHeadersEnd': 0.975}, 'protocol': 'h2', 'securityState': 'secure', 'securityDetails': {'protocol': 'TLS 1.2', 'keyExchange': 'ECDHE_RSA', 'keyExchangeGroup': 'X25519', 'cipher': 'AES_128_GCM', 'certificateId': 0, 'subjectName': 'www.python.org', 'sanList': ['www.python.org', 'docs.python.org', 'bugs.python.org', 'wiki.python.org', 'hg.python.org', 'mail.python.org', 'pypi.python.org', 'packaging.python.org', 'login.python.org', 'discuss.python.org', 'us.pycon.org', 'pypi.io', 'docs.pypi.io', 'pypi.org', 'docs.pypi.org', 'donate.pypi.org', 'devguide.python.org', 'www.bugs.python.org', 'python.org'], 'issuer': 'DigiCert SHA2 Extended Validation Server CA', 'validFrom': 1537228800, 'validTo': 1602676800, 'signedCertificateTimestampList': [], 'certificateTransparencyCompliance': 'unknown'}}, 'frameId': '882CFDEEA07EB00A5E7510ADD2A39F22'}}\n request = await task\n # {'id': 30, 'result': {'body': '\\n