pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. biolib/__init__.py +357 -11
  2. biolib/_data_record/data_record.py +380 -0
  3. biolib/_index/__init__.py +0 -0
  4. biolib/_index/index.py +55 -0
  5. biolib/_index/query_result.py +103 -0
  6. biolib/_internal/__init__.py +0 -0
  7. biolib/_internal/add_copilot_prompts.py +58 -0
  8. biolib/_internal/add_gui_files.py +81 -0
  9. biolib/_internal/data_record/__init__.py +1 -0
  10. biolib/_internal/data_record/data_record.py +85 -0
  11. biolib/_internal/data_record/push_data.py +116 -0
  12. biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
  13. biolib/_internal/errors.py +5 -0
  14. biolib/_internal/file_utils.py +125 -0
  15. biolib/_internal/fuse_mount/__init__.py +1 -0
  16. biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
  17. biolib/_internal/http_client.py +159 -0
  18. biolib/_internal/lfs/__init__.py +1 -0
  19. biolib/_internal/lfs/cache.py +51 -0
  20. biolib/_internal/libs/__init__.py +1 -0
  21. biolib/_internal/libs/fusepy/__init__.py +1257 -0
  22. biolib/_internal/push_application.py +488 -0
  23. biolib/_internal/runtime.py +22 -0
  24. biolib/_internal/string_utils.py +13 -0
  25. biolib/_internal/templates/__init__.py +1 -0
  26. biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
  27. biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
  28. biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
  29. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  30. biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
  31. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  32. biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
  33. biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
  34. biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
  35. biolib/_internal/templates/gitignore_template/.gitignore +10 -0
  36. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  37. biolib/_internal/templates/gui_template/App.tsx +53 -0
  38. biolib/_internal/templates/gui_template/Dockerfile +27 -0
  39. biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
  40. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  41. biolib/_internal/templates/gui_template/index.css +5 -0
  42. biolib/_internal/templates/gui_template/index.html +13 -0
  43. biolib/_internal/templates/gui_template/index.tsx +10 -0
  44. biolib/_internal/templates/gui_template/package.json +27 -0
  45. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  46. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
  47. biolib/_internal/templates/gui_template/vite.config.mts +10 -0
  48. biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
  49. biolib/_internal/templates/init_template/Dockerfile +14 -0
  50. biolib/_internal/templates/init_template/requirements.txt +1 -0
  51. biolib/_internal/templates/init_template/run.py +12 -0
  52. biolib/_internal/templates/init_template/run.sh +4 -0
  53. biolib/_internal/templates/templates.py +25 -0
  54. biolib/_internal/tree_utils.py +106 -0
  55. biolib/_internal/utils/__init__.py +65 -0
  56. biolib/_internal/utils/auth.py +46 -0
  57. biolib/_internal/utils/job_url.py +33 -0
  58. biolib/_internal/utils/multinode.py +263 -0
  59. biolib/_runtime/runtime.py +157 -0
  60. biolib/_session/session.py +44 -0
  61. biolib/_shared/__init__.py +0 -0
  62. biolib/_shared/types/__init__.py +74 -0
  63. biolib/_shared/types/account.py +12 -0
  64. biolib/_shared/types/account_member.py +8 -0
  65. biolib/_shared/types/app.py +9 -0
  66. biolib/_shared/types/data_record.py +40 -0
  67. biolib/_shared/types/experiment.py +32 -0
  68. biolib/_shared/types/file_node.py +17 -0
  69. biolib/_shared/types/push.py +6 -0
  70. biolib/_shared/types/resource.py +37 -0
  71. biolib/_shared/types/resource_deploy_key.py +11 -0
  72. biolib/_shared/types/resource_permission.py +14 -0
  73. biolib/_shared/types/resource_version.py +19 -0
  74. biolib/_shared/types/result.py +14 -0
  75. biolib/_shared/types/typing.py +10 -0
  76. biolib/_shared/types/user.py +19 -0
  77. biolib/_shared/utils/__init__.py +7 -0
  78. biolib/_shared/utils/resource_uri.py +75 -0
  79. biolib/api/__init__.py +6 -0
  80. biolib/api/client.py +168 -0
  81. biolib/app/app.py +252 -49
  82. biolib/app/search_apps.py +45 -0
  83. biolib/biolib_api_client/api_client.py +126 -31
  84. biolib/biolib_api_client/app_types.py +24 -4
  85. biolib/biolib_api_client/auth.py +31 -8
  86. biolib/biolib_api_client/biolib_app_api.py +147 -52
  87. biolib/biolib_api_client/biolib_job_api.py +161 -141
  88. biolib/biolib_api_client/job_types.py +21 -5
  89. biolib/biolib_api_client/lfs_types.py +7 -23
  90. biolib/biolib_api_client/user_state.py +56 -0
  91. biolib/biolib_binary_format/__init__.py +1 -4
  92. biolib/biolib_binary_format/file_in_container.py +105 -0
  93. biolib/biolib_binary_format/module_input.py +24 -7
  94. biolib/biolib_binary_format/module_output_v2.py +149 -0
  95. biolib/biolib_binary_format/remote_endpoints.py +34 -0
  96. biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  97. biolib/biolib_binary_format/saved_job.py +3 -2
  98. biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
  99. biolib/biolib_binary_format/system_status_update.py +3 -2
  100. biolib/biolib_binary_format/utils.py +175 -0
  101. biolib/biolib_docker_client/__init__.py +11 -2
  102. biolib/biolib_errors.py +36 -0
  103. biolib/biolib_logging.py +27 -10
  104. biolib/cli/__init__.py +38 -0
  105. biolib/cli/auth.py +46 -0
  106. biolib/cli/data_record.py +164 -0
  107. biolib/cli/index.py +32 -0
  108. biolib/cli/init.py +421 -0
  109. biolib/cli/lfs.py +101 -0
  110. biolib/cli/push.py +50 -0
  111. biolib/cli/run.py +63 -0
  112. biolib/cli/runtime.py +14 -0
  113. biolib/cli/sdk.py +16 -0
  114. biolib/cli/start.py +56 -0
  115. biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
  116. biolib/compute_node/job_worker/cache_state.py +66 -88
  117. biolib/compute_node/job_worker/cache_types.py +1 -6
  118. biolib/compute_node/job_worker/docker_image_cache.py +112 -37
  119. biolib/compute_node/job_worker/executors/__init__.py +0 -3
  120. biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
  121. biolib/compute_node/job_worker/executors/docker_types.py +9 -1
  122. biolib/compute_node/job_worker/executors/types.py +19 -9
  123. biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
  124. biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
  125. biolib/compute_node/job_worker/job_storage.py +108 -0
  126. biolib/compute_node/job_worker/job_worker.py +397 -212
  127. biolib/compute_node/job_worker/large_file_system.py +87 -38
  128. biolib/compute_node/job_worker/network_alloc.py +99 -0
  129. biolib/compute_node/job_worker/network_buffer.py +240 -0
  130. biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
  131. biolib/compute_node/job_worker/utils.py +9 -24
  132. biolib/compute_node/remote_host_proxy.py +400 -98
  133. biolib/compute_node/utils.py +31 -9
  134. biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
  135. biolib/compute_node/webserver/proxy_utils.py +28 -0
  136. biolib/compute_node/webserver/webserver.py +130 -44
  137. biolib/compute_node/webserver/webserver_types.py +2 -6
  138. biolib/compute_node/webserver/webserver_utils.py +77 -12
  139. biolib/compute_node/webserver/worker_thread.py +183 -42
  140. biolib/experiments/__init__.py +0 -0
  141. biolib/experiments/experiment.py +356 -0
  142. biolib/jobs/__init__.py +1 -0
  143. biolib/jobs/job.py +741 -0
  144. biolib/jobs/job_result.py +185 -0
  145. biolib/jobs/types.py +50 -0
  146. biolib/py.typed +0 -0
  147. biolib/runtime/__init__.py +14 -0
  148. biolib/sdk/__init__.py +91 -0
  149. biolib/tables.py +34 -0
  150. biolib/typing_utils.py +2 -7
  151. biolib/user/__init__.py +1 -0
  152. biolib/user/sign_in.py +54 -0
  153. biolib/utils/__init__.py +162 -0
  154. biolib/utils/cache_state.py +94 -0
  155. biolib/utils/multipart_uploader.py +194 -0
  156. biolib/utils/seq_util.py +150 -0
  157. biolib/utils/zip/remote_zip.py +640 -0
  158. pybiolib-1.2.1890.dist-info/METADATA +41 -0
  159. pybiolib-1.2.1890.dist-info/RECORD +177 -0
  160. {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
  161. pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
  162. README.md +0 -17
  163. biolib/app/app_result.py +0 -68
  164. biolib/app/utils.py +0 -62
  165. biolib/biolib-js/0-biolib.worker.js +0 -1
  166. biolib/biolib-js/1-biolib.worker.js +0 -1
  167. biolib/biolib-js/2-biolib.worker.js +0 -1
  168. biolib/biolib-js/3-biolib.worker.js +0 -1
  169. biolib/biolib-js/4-biolib.worker.js +0 -1
  170. biolib/biolib-js/5-biolib.worker.js +0 -1
  171. biolib/biolib-js/6-biolib.worker.js +0 -1
  172. biolib/biolib-js/index.html +0 -10
  173. biolib/biolib-js/main-biolib.js +0 -1
  174. biolib/biolib_api_client/biolib_account_api.py +0 -21
  175. biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
  176. biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
  177. biolib/biolib_binary_format/module_output.py +0 -58
  178. biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
  179. biolib/biolib_push.py +0 -114
  180. biolib/cli.py +0 -203
  181. biolib/cli_utils.py +0 -273
  182. biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
  183. biolib/compute_node/enclave/__init__.py +0 -2
  184. biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
  185. biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
  186. biolib/compute_node/job_worker/executors/base_executor.py +0 -18
  187. biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
  188. biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
  189. biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
  190. biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
  191. biolib/lfs.py +0 -196
  192. biolib/pyppeteer/.circleci/config.yml +0 -100
  193. biolib/pyppeteer/.coveragerc +0 -3
  194. biolib/pyppeteer/.gitignore +0 -89
  195. biolib/pyppeteer/.pre-commit-config.yaml +0 -28
  196. biolib/pyppeteer/CHANGES.md +0 -253
  197. biolib/pyppeteer/CONTRIBUTING.md +0 -26
  198. biolib/pyppeteer/LICENSE +0 -12
  199. biolib/pyppeteer/README.md +0 -137
  200. biolib/pyppeteer/docs/Makefile +0 -177
  201. biolib/pyppeteer/docs/_static/custom.css +0 -28
  202. biolib/pyppeteer/docs/_templates/layout.html +0 -10
  203. biolib/pyppeteer/docs/changes.md +0 -1
  204. biolib/pyppeteer/docs/conf.py +0 -299
  205. biolib/pyppeteer/docs/index.md +0 -21
  206. biolib/pyppeteer/docs/make.bat +0 -242
  207. biolib/pyppeteer/docs/reference.md +0 -211
  208. biolib/pyppeteer/docs/server.py +0 -60
  209. biolib/pyppeteer/poetry.lock +0 -1699
  210. biolib/pyppeteer/pyppeteer/__init__.py +0 -135
  211. biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
  212. biolib/pyppeteer/pyppeteer/browser.py +0 -401
  213. biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
  214. biolib/pyppeteer/pyppeteer/command.py +0 -22
  215. biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
  216. biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
  217. biolib/pyppeteer/pyppeteer/coverage.py +0 -346
  218. biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
  219. biolib/pyppeteer/pyppeteer/dialog.py +0 -79
  220. biolib/pyppeteer/pyppeteer/domworld.py +0 -597
  221. biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
  222. biolib/pyppeteer/pyppeteer/errors.py +0 -48
  223. biolib/pyppeteer/pyppeteer/events.py +0 -63
  224. biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
  225. biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
  226. biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
  227. biolib/pyppeteer/pyppeteer/helpers.py +0 -245
  228. biolib/pyppeteer/pyppeteer/input.py +0 -371
  229. biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
  230. biolib/pyppeteer/pyppeteer/launcher.py +0 -683
  231. biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
  232. biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
  233. biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
  234. biolib/pyppeteer/pyppeteer/multimap.py +0 -82
  235. biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
  236. biolib/pyppeteer/pyppeteer/options.py +0 -8
  237. biolib/pyppeteer/pyppeteer/page.py +0 -1728
  238. biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
  239. biolib/pyppeteer/pyppeteer/target.py +0 -147
  240. biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
  241. biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
  242. biolib/pyppeteer/pyppeteer/tracing.py +0 -93
  243. biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
  244. biolib/pyppeteer/pyppeteer/util.py +0 -18
  245. biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
  246. biolib/pyppeteer/pyppeteer/worker.py +0 -101
  247. biolib/pyppeteer/pyproject.toml +0 -97
  248. biolib/pyppeteer/spell.txt +0 -137
  249. biolib/pyppeteer/tox.ini +0 -72
  250. biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
  251. biolib/start_cli.py +0 -7
  252. biolib/utils.py +0 -47
  253. biolib/validators/validate_app_version.py +0 -183
  254. biolib/validators/validate_argument.py +0 -134
  255. biolib/validators/validate_module.py +0 -323
  256. biolib/validators/validate_zip_file.py +0 -40
  257. biolib/validators/validator_utils.py +0 -103
  258. pybiolib-0.2.951.dist-info/LICENSE +0 -21
  259. pybiolib-0.2.951.dist-info/METADATA +0 -61
  260. pybiolib-0.2.951.dist-info/RECORD +0 -153
  261. pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
  262. /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
@@ -1,1728 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- """Page module."""
5
-
6
- import asyncio
7
- import base64
8
- import inspect
9
- import json
10
- import logging
11
- import math
12
- import mimetypes
13
- import re
14
- import sys
15
- import warnings
16
- from copy import copy
17
- from pathlib import Path
18
- from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Sequence, Union
19
-
20
- from pyee import AsyncIOEventEmitter
21
- from biolib.pyppeteer.pyppeteer import helpers
22
- from biolib.pyppeteer.pyppeteer.accessibility import Accessibility
23
- from biolib.pyppeteer.pyppeteer.connection import CDPSession, Connection
24
- from biolib.pyppeteer.pyppeteer.coverage import Coverage
25
- from biolib.pyppeteer.pyppeteer.dialog import Dialog
26
- from biolib.pyppeteer.pyppeteer.emulation_manager import EmulationManager
27
- from biolib.pyppeteer.pyppeteer.errors import BrowserError, PageError
28
- from biolib.pyppeteer.pyppeteer.events import Events
29
- from biolib.pyppeteer.pyppeteer.frame import Frame, FrameManager
30
- from biolib.pyppeteer.pyppeteer.input import Keyboard, Mouse, Touchscreen
31
- from biolib.pyppeteer.pyppeteer.jshandle import ElementHandle, JSHandle, createJSHandle
32
- from biolib.pyppeteer.pyppeteer.models import JSFunctionArg, MouseButton, Protocol, ScreenshotClip, WaitTargets
33
- from biolib.pyppeteer.pyppeteer.network_manager import Request, Response
34
- from biolib.pyppeteer.pyppeteer.task_queue import TaskQueue
35
- from biolib.pyppeteer.pyppeteer.timeout_settings import TimeoutSettings
36
- from biolib.pyppeteer.pyppeteer.tracing import Tracing
37
- from biolib.pyppeteer.pyppeteer.worker import Worker
38
-
39
- if sys.version_info < (3, 8):
40
- from typing_extensions import Literal
41
- else:
42
- from typing import Literal
43
-
44
- if TYPE_CHECKING:
45
- from pyppeteer.target import Target
46
- from pyppeteer.browser import Browser, BrowserContext
47
-
48
- logger = logging.getLogger(__name__)
49
-
50
-
51
- class Page(AsyncIOEventEmitter):
52
- """Page class.
53
-
54
- This class provides methods to interact with a single tab of chrome. One
55
- :class:`~pyppeteer.browser.Browser` object might have multiple Page object.
56
-
57
- The :class:`Page` class emits various :attr:`~Events.Page` which can be
58
- handled by using ``on`` or ``once`` method, which is inherited from
59
- `pyee <https://pyee.readthedocs.io/en/latest/>`_'s ``AsyncIOEventEmitter`` class.
60
- """
61
-
62
- PaperFormats: Dict[str, Dict[str, float]] = {
63
- 'letter': {'width': 8.5, 'height': 11},
64
- 'legal': {'width': 8.5, 'height': 14},
65
- 'tabloid': {'width': 11, 'height': 17},
66
- 'ledger': {'width': 17, 'height': 11},
67
- 'a0': {'width': 33.1, 'height': 46.8},
68
- 'a1': {'width': 23.4, 'height': 33.1},
69
- 'a2': {'width': 16.5, 'height': 23.4},
70
- 'a3': {'width': 11.7, 'height': 16.5},
71
- 'a4': {'width': 8.27, 'height': 11.7},
72
- 'a5': {'width': 5.83, 'height': 8.27},
73
- }
74
-
75
- @staticmethod
76
- async def create(
77
- client: CDPSession,
78
- target: 'Target',
79
- ignoreHTTPSErrors: bool,
80
- defaultViewport: Protocol.Page.Viewport,
81
- screenshotTaskQueue: TaskQueue = None,
82
- ) -> 'Page':
83
- """Async function which makes new page object."""
84
- page = Page(
85
- client=client, target=target, ignoreHTTPSErrors=ignoreHTTPSErrors, screenshotTaskQueue=screenshotTaskQueue
86
- )
87
- await page._initialize()
88
- if defaultViewport:
89
- await page.setViewport(defaultViewport)
90
- return page
91
-
92
- def __init__(
93
- self, client: CDPSession, target: 'Target', ignoreHTTPSErrors: bool, screenshotTaskQueue: TaskQueue = None
94
- ) -> None:
95
- super().__init__()
96
- self._closed = False
97
- self._client = client
98
- self._target = target
99
- self._keyboard = Keyboard(client)
100
- self._mouse = Mouse(client, self._keyboard)
101
- self._timeoutSettings = TimeoutSettings()
102
- self._touchscreen = Touchscreen(client, self._keyboard)
103
- self._accessibility = Accessibility(client)
104
- self._frameManager = FrameManager(client, self, ignoreHTTPSErrors, self._timeoutSettings)
105
- self._emulationManager = EmulationManager(client)
106
- self._tracing = Tracing(client)
107
- self._pageBindings: Dict[str, Callable[..., Any]] = {}
108
- self._coverage = Coverage(client)
109
- self._javascriptEnabled = True
110
- self._viewport: Optional[Protocol.Page.Viewport] = None
111
-
112
- if screenshotTaskQueue is None:
113
- screenshotTaskQueue = TaskQueue()
114
- self._screenshotTaskQueue = screenshotTaskQueue
115
-
116
- self._workers: Dict[str, Worker] = {}
117
- self._disconnectPromise = None
118
-
119
- async def _onTargetAttached(event: Dict) -> None:
120
- targetInfo = event['targetInfo']
121
- if targetInfo['type'] != 'worker':
122
- # If we don't detach from service workers, they will never die.
123
- try:
124
- await client.send('Target.detachFromTarget', {'sessionId': event['sessionId'],})
125
- except Exception as e:
126
- logger.exception(f'An exception occurred while trying to detach from target')
127
- return
128
- sessionId = event['sessionId']
129
- session = Connection.fromSession(client).session(sessionId)
130
- worker = Worker(session, targetInfo['url'], self._addConsoleMessage, self._handleException,)
131
- self._workers[sessionId] = worker
132
- self.emit(Events.Page.WorkerCreated, worker)
133
-
134
- def _onTargetDetached(event: Dict) -> None:
135
- sessionId = event['sessionId']
136
- worker = self._workers.get(sessionId)
137
- if worker is None:
138
- return
139
- self.emit(Events.Page.WorkerDestroyed, worker)
140
- del self._workers[sessionId]
141
-
142
- client.on('Target.attachedToTarget', _onTargetAttached)
143
- client.on('Target.detachedFromTarget', _onTargetDetached)
144
-
145
- _fm = self._frameManager
146
- _fm.on(Events.FrameManager.FrameAttached, lambda event: self.emit(Events.Page.FrameAttached, event))
147
- _fm.on(Events.FrameManager.FrameDetached, lambda event: self.emit(Events.Page.FrameDetached, event))
148
- _fm.on(Events.FrameManager.FrameNavigated, lambda event: self.emit(Events.Page.FrameNavigated, event))
149
-
150
- networkManager = self._frameManager.networkManager
151
- _nm = networkManager
152
- _nm.on(Events.NetworkManager.Request, lambda event: self.emit(Events.Page.Request, event))
153
- _nm.on(Events.NetworkManager.Response, lambda event: self.emit(Events.Page.Response, event))
154
- _nm.on(Events.NetworkManager.RequestFailed, lambda event: self.emit(Events.Page.RequestFailed, event))
155
- _nm.on(Events.NetworkManager.RequestFinished, lambda event: self.emit(Events.Page.RequestFinished, event))
156
- self._fileChooserInterceptors = set()
157
-
158
- client.on('Page.domContentEventFired', lambda event: self.emit(Events.Page.DOMContentLoaded))
159
- client.on('Page.loadEventFired', lambda event: self.emit(Events.Page.Load))
160
- client.on('Runtime.consoleAPICalled', lambda event: self._onConsoleAPI(event))
161
- client.on('Runtime.bindingCalled', lambda event: self._onBindingCalled(event))
162
- client.on('Page.javascriptDialogOpening', lambda event: self._onDialog(event))
163
- client.on('Runtime.exceptionThrown', lambda exception: self._handleException(exception.get('exceptionDetails')))
164
- client.on('Inspector.targetCrashed', lambda event: self._onTargetCrashed())
165
- client.on('Performance.metrics', lambda event: self._emitMetrics(event))
166
- client.on('Log.entryAdded', lambda event: self._onLogEntryAdded(event))
167
- client.on('Page.fileChooserOpened', lambda event: self._onFileChooser(event))
168
-
169
- def closed(*_: Any) -> None:
170
- self.emit(Events.Page.Close)
171
- self._closed = True
172
-
173
- self._target._isClosedPromise.add_done_callback(closed)
174
-
175
- async def _initialize(self) -> None:
176
- await asyncio.gather(
177
- self._frameManager.initialize(),
178
- self._client.send(
179
- 'Target.setAutoAttach', {'autoAttach': True, 'waitForDebuggerOnStart': False, 'flatten': True,}
180
- ),
181
- self._client.send('Performance.enable'),
182
- self._client.send('Log.enable'),
183
- )
184
-
185
- async def _onFileChooser(self, event: Dict) -> None:
186
- if not self._fileChooserInterceptors:
187
- return
188
- frame = self._frameManager.frame(event['frameId'])
189
- context = await frame.executionContext
190
- if context is None:
191
- raise BrowserError(f'Frame {frame} execution\'s context is not defined')
192
- element = await context._adoptBackednNodeId(event['backendNodeId'])
193
- interceptors = copy(self._fileChooserInterceptors)
194
- self._fileChooserInterceptors.clear()
195
- fileChooser = FileChooser(self._client, element, event)
196
- for interceptor in interceptors:
197
- interceptor.call(None, fileChooser)
198
-
199
- async def waitForFileChooser(self, timeout: float = None):
200
- if not self._fileChooserInterceptors:
201
- await self._client.send('Page.setInterceptFileChooserDialog', {'enabled': True})
202
- if not timeout:
203
- timeout = self._timeoutSettings.timeout
204
-
205
- promise = self._loop.create_future()
206
- callback = promise.result
207
- self._fileChooserInterceptors.add(callback())
208
- try:
209
- return await asyncio.wait_for(promise, timeout=timeout)
210
- except Exception as e:
211
- self._fileChooserInterceptors.remove(callback())
212
- raise e
213
-
214
- async def setGeolocation(self, longitude: float, latitude: float, accuracy: float = 0) -> None:
215
- if -180 >= longitude >= 180:
216
- raise PageError(f'Invalid longitude {longitude}: precondition -180 <= LONGITUDE <= 180 failed')
217
- if -90 >= latitude >= 90:
218
- raise PageError(f'Invalid latitude {latitude}: precondition -90 <= LATITUDE <= 90 failed')
219
- if accuracy < 0:
220
- raise PageError(f'Invalid accuracy {accuracy}: precondition ACCURACY >= 0')
221
- await self._client.send(
222
- 'Emulation.setGeolocationOverride', {'longitude': longitude, 'latitude': latitude, 'accuracy': accuracy,}
223
- )
224
-
225
- @property
226
- def target(self) -> 'Target':
227
- """Return a target this page created from."""
228
- return self._target
229
-
230
- @property
231
- def browser(self) -> 'Browser':
232
- """Get the browser the page belongs to."""
233
- return self._target.browser
234
-
235
- @property
236
- def browserContext(self) -> 'BrowserContext':
237
- return self._target.browserContext
238
-
239
- def _onTargetCrashed(self) -> None:
240
- self.emit('error', PageError('Page crashed!'))
241
-
242
- def _onLogEntryAdded(self, event: Dict) -> None:
243
- entry = event.get('entry', {})
244
- level = entry.get('level', '')
245
- text = entry.get('text', '')
246
- args = entry.get('args', [])
247
- source = entry.get('source', '')
248
- url = entry.get('url', '')
249
- lineNumber = entry.get('lineNumber')
250
- for arg in args:
251
- helpers.releaseObject(self._client, arg)
252
-
253
- if source != 'worker':
254
- self.emit(Events.Page.Console, ConsoleMessage(level, text, location={'url': url, 'lineNumber': lineNumber}))
255
-
256
- @property
257
- def mainFrame(self) -> Frame:
258
- """Get main :class:`~pyppeteer.frame_manager.Frame` of this page."""
259
- if self._frameManager._mainFrame is not None:
260
- return self._frameManager._mainFrame
261
- raise RuntimeError(f'No mainFrame attribute exists for class instance {self}')
262
-
263
- @property
264
- def keyboard(self) -> Keyboard:
265
- """Get :class:`~pyppeteer.input.Keyboard` object."""
266
- return self._keyboard
267
-
268
- @property
269
- def touchscreen(self) -> Touchscreen:
270
- """Get :class:`~pyppeteer.input.Touchscreen` object."""
271
- return self._touchscreen
272
-
273
- @property
274
- def coverage(self) -> Coverage:
275
- """Return :class:`~pyppeteer.coverage.Coverage`."""
276
- return self._coverage
277
-
278
- @property
279
- def tracing(self) -> Tracing:
280
- return self._tracing
281
-
282
- @property
283
- def accessibility(self) -> Accessibility:
284
- return self._accessibility
285
-
286
- @property
287
- def frames(self) -> List['Frame']:
288
- return self._frameManager.frames
289
-
290
- @property
291
- def workers(self) -> List[Worker]:
292
- """Get all workers of this page."""
293
- return list(self._workers.values())
294
-
295
- async def setRequestInterception(self, value: bool) -> None:
296
- """Enable/disable request interception.
297
-
298
- Activating request interception enables
299
- :class:`~pyppeteer.network_manager.Request` class's
300
- :meth:`~pyppeteer.network_manager.Request.abort`,
301
- :meth:`~pyppeteer.network_manager.Request.continue_`, and
302
- :meth:`~pyppeteer.network_manager.Request.response` methods.
303
- This provides the capability to modify network requests that are made
304
- by a page.
305
-
306
- Once request interception is enabled, every request will stall unless
307
- it's continued, responded or aborted.
308
-
309
- An example of a native request interceptor that aborts all image
310
- requests:
311
-
312
- .. code:: python
313
-
314
- browser = await launch()
315
- page = await browser.newPage()
316
- await page.setRequestInterception(True)
317
-
318
- async def intercept(request):
319
- if request.url.endswith('.png') or request.url.endswith('.jpg'):
320
- await request.abort()
321
- else:
322
- await request.continue_()
323
-
324
- page.on('request', lambda req: asyncio.ensure_future(intercept(req)))
325
- await page.goto('https://example.com')
326
- await browser.close()
327
- """
328
- return await self._frameManager.networkManager.setRequestInterception(value)
329
-
330
- async def setOfflineMode(self, enabled: bool) -> None:
331
- """Set offline mode enable/disable."""
332
- await self._frameManager.networkManager.setOfflineMode(enabled)
333
-
334
- def setDefaultNavigationTimeout(self, timeout: int) -> None:
335
- """Change the default maximum navigation timeout.
336
-
337
- This method changes the default timeout of 30 seconds for the following
338
- methods:
339
-
340
- * :meth:`goto`
341
- * :meth:`goBack`
342
- * :meth:`goForward`
343
- * :meth:`reload`
344
- * :meth:`waitForNavigation`
345
-
346
- :arg int timeout: Maximum navigation time in milliseconds. Pass ``0``
347
- to disable timeout.
348
- """
349
- self._timeoutSettings.setDefaultNavigationTimeout(timeout)
350
-
351
- def setDefaultTimeout(self, timeout: float) -> None:
352
- self._timeoutSettings.setDefaultTimeout(timeout)
353
-
354
- async def querySelector(self, selector: str) -> Optional[ElementHandle]:
355
- """Get an Element which matches ``selector``.
356
-
357
- :arg str selector: A selector to search element.
358
- :return Optional[ElementHandle]: If element which matches the
359
- ``selector`` is found, return its
360
- :class:`~pyppeteer.element_handle.ElementHandle`. If not found,
361
- returns ``None``.
362
- """
363
- return await self.mainFrame.querySelector(selector)
364
-
365
- async def evaluateHandle(self, pageFunction: str, *args: Any) -> JSHandle:
366
- """Execute function on this page.
367
-
368
- Difference between :meth:`~pyppeteer.page.Page.evaluate` and
369
- :meth:`~pyppeteer.page.Page.evaluateHandle` is that
370
- ``evaluateHandle`` returns JSHandle object (not value).
371
-
372
- :arg str pageFunction: JavaScript function to be executed.
373
- """
374
- context = await self.mainFrame.executionContext
375
- return await context.evaluateHandle(pageFunction, *args)
376
-
377
- async def queryObjects(self, prototypeHandle: JSHandle) -> JSHandle:
378
- """Iterate js heap and finds all the objects with the handle.
379
-
380
- :arg JSHandle prototypeHandle: JSHandle of prototype object.
381
- """
382
- context = await self.mainFrame.executionContext
383
- return await context.queryObjects(prototypeHandle)
384
-
385
- async def querySelectorEval(self, selector: str, pageFunction: str, *args: JSFunctionArg) -> Any:
386
- """Execute function with an element which matches ``selector``.
387
-
388
- :arg str selector: A selector to query page for.
389
- :arg str pageFunction: String of JavaScript function to be evaluated on
390
- browser. This function takes an element which
391
- matches the selector as a first argument.
392
- :arg Any args: Arguments to pass to ``pageFunction``.
393
-
394
- This method raises error if no element matched the ``selector``.
395
- """
396
- return await self.mainFrame.querySelectorEval(selector, pageFunction, *args)
397
-
398
- async def querySelectorAllEval(self, selector: str, pageFunction: str, *args: JSFunctionArg) -> Any:
399
- """Execute function with all elements which matches ``selector``.
400
-
401
- :arg str selector: A selector to query page for.
402
- :arg str pageFunction: String of JavaScript function to be evaluated on
403
- browser. This function takes Array of the
404
- matched elements as the first argument.
405
- :arg Any args: Arguments to pass to ``pageFunction``.
406
- """
407
- return await self.mainFrame.querySelectorAllEval(selector, pageFunction, *args)
408
-
409
- async def querySelectorAll(self, selector: str) -> List[ElementHandle]:
410
- """Get all element which matches ``selector`` as a list.
411
-
412
- :arg str selector: A selector to search element.
413
- :return List[ElementHandle]: List of
414
- :class:`~pyppeteer.element_handle.ElementHandle` which matches the
415
- ``selector``. If no element is matched to the ``selector``, return
416
- empty list.
417
- """
418
- return await self.mainFrame.querySelectorAll(selector)
419
-
420
- async def xpath(self, expression: str) -> List[ElementHandle]:
421
- """Evaluate the XPath expression.
422
-
423
- If there are no such elements in this page, return an empty list.
424
-
425
- :arg str expression: XPath string to be evaluated.
426
- """
427
- return await self.mainFrame.xpath(expression)
428
-
429
- # Shortcut aliases
430
- J = querySelector
431
- Jeval = querySelectorEval
432
- JJ = querySelectorAll
433
- JJeval = querySelectorAllEval
434
- Jx = xpath
435
-
436
- async def cookies(self, *urls: Sequence[str]) -> List[Dict[str, Union[str, int, bool]]]:
437
- """Get cookies.
438
-
439
- If no URLs are specified, this method returns cookies for the current
440
- page URL. If URLs are specified, only cookies for those URLs are
441
- returned.
442
-
443
- Returned cookies are list of dictionaries which contain these fields:
444
-
445
- * ``name`` (str)
446
- * ``value`` (str)
447
- * ``url`` (str)
448
- * ``domain`` (str)
449
- * ``path`` (str)
450
- * ``expires`` (number): Unix time in seconds
451
- * ``httpOnly`` (bool)
452
- * ``secure`` (bool)
453
- * ``session`` (bool)
454
- * ``sameSite`` (str): ``'Strict'`` or ``'Lax'``
455
- """
456
- resp = await self._client.send('Network.getCookies', {'urls': urls or [self.url],})
457
- return resp.get('cookies', {})
458
-
459
- async def deleteCookie(self, *cookies: dict) -> None:
460
- """Delete cookie.
461
-
462
- ``cookies`` should be dictionaries which contain these fields:
463
-
464
- * ``name`` (str): **required**
465
- * ``url`` (str)
466
- * ``domain`` (str)
467
- * ``path`` (str)
468
- * ``secure`` (bool)
469
- """
470
- pageURL = self.url
471
- for cookie in cookies:
472
- item = cookie.copy()
473
- if not cookie.get('url') and pageURL.startswith('http'):
474
- item['url'] = pageURL
475
- await self._client.send('Network.deleteCookies', item)
476
-
477
- async def setCookie(self, *cookies: dict) -> None:
478
- """Set cookies.
479
-
480
- ``cookies`` should be dictionaries which contain these fields:
481
-
482
- * ``name`` (str): **required**
483
- * ``value`` (str): **required**
484
- * ``url`` (str)
485
- * ``domain`` (str)
486
- * ``path`` (str)
487
- * ``expires`` (number): Unix time in seconds
488
- * ``httpOnly`` (bool)
489
- * ``secure`` (bool)
490
- * ``sameSite`` (str): ``'Strict'`` or ``'Lax'``
491
- """
492
- pageURL = self.url
493
- startsWithHTTP = pageURL.startswith('http')
494
- items = []
495
- for cookie in cookies:
496
- item = cookie.copy()
497
- if 'url' not in item and startsWithHTTP:
498
- item['url'] = pageURL
499
- if item.get('url') == 'about:blank':
500
- name = item.get('name', '')
501
- raise PageError(f'Blank page can not have cookie "{name}"')
502
- if item.get('url', '').startswith('data:'):
503
- name = item.get('name', '')
504
- raise PageError(f'Data URL page can not have cookie "{name}"')
505
- items.append(item)
506
- await self.deleteCookie(*items)
507
- if items:
508
- await self._client.send('Network.setCookies', {'cookies': items,})
509
-
510
- async def addScriptTag(
511
- self, url: str = None, path: str = None, content: str = None, _type: str = None
512
- ) -> ElementHandle:
513
- """Add script tag to this page.
514
-
515
- One of ``url``, ``path`` or ``content`` option is necessary.
516
- * ``url`` (string): URL of a script to add.
517
- * ``path`` (string): Path to the local JavaScript file to add.
518
- * ``content`` (string): JavaScript string to add.
519
- * ``type`` (string): Script type. Use ``module`` in order to load a
520
- JavaScript ES6 module.
521
-
522
- :return ElementHandle: :class:`~pyppeteer.element_handle.ElementHandle`
523
- of added tag.
524
- """
525
- return await self.mainFrame.addScriptTag(url=url, path=path, content=content, type_=_type)
526
-
527
- async def addStyleTag(
528
- self, url: Optional[str] = None, path: Optional[Union[Path, str]] = None, content: Optional[str] = None
529
- ) -> ElementHandle:
530
- """Add style or link tag to this page.
531
-
532
- One of ``url``, ``path`` or ``content`` option is necessary.
533
- * ``url`` (string): URL of the link tag to add.
534
- * ``path`` (string): Path to the local CSS file to add.
535
- * ``content`` (string): CSS string to add.
536
-
537
- :return ElementHandle: :class:`~pyppeteer.element_handle.ElementHandle`
538
- of added tag.
539
- """
540
- return await self.mainFrame.addStyleTag(url=url, path=path, content=content)
541
-
542
- async def exposeFunction(self, name: str, pyppeteerFunction: Callable[..., Any]) -> None:
543
- """Add python function to the browser's ``window`` object as ``name``.
544
-
545
- Registered function can be called from chrome process.
546
-
547
- :arg string name: Name of the function on the window object.
548
- :arg Callable pyppeteerFunction: Function which will be called in
549
- python process.
550
- """
551
- if self._pageBindings.get(name):
552
- raise PageError(f'Failed to add page binding with name {name}: window["{name}"] already exists!')
553
- self._pageBindings[name] = pyppeteerFunction
554
-
555
- addPageBinding = '''
556
- function addPageBinding(bindingName) {
557
- const binding = window[bindingName];
558
- window[bindingName] = (...args) => {
559
- const me = window[bindingName];
560
- let callbacks = me['callbacks'];
561
- if (!callbacks) {
562
- callbacks = new Map();
563
- me['callbacks'] = callbacks;
564
- }
565
- const seq = (me['lastSeq'] || 0) + 1;
566
- me['lastSeq'] = seq;
567
- const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
568
- binding(JSON.stringify({name: bindingName, seq, args}));
569
- return promise;
570
- };
571
- }
572
- '''
573
- expression = helpers.evaluationString(addPageBinding, name)
574
- await self._client.send('Runtime.addBinding', {'name': name})
575
- await self._client.send('Page.addScriptToEvaluateOnNewDocument', {'source': expression})
576
-
577
- async def _evaluate(frame: Frame) -> None:
578
- try:
579
- await frame.evaluate(expression)
580
- except Exception as e:
581
- logger.error(f'An exception occurred: {e}')
582
-
583
- await asyncio.gather(*(_evaluate(frame) for frame in self.frames))
584
-
585
- async def authenticate(self, credentials: Dict[str, str]) -> Any:
586
- """Provide credentials for http authentication.
587
-
588
- ``credentials`` should be ``None`` or dict which has ``username`` and
589
- ``password`` field.
590
- """
591
- return await self._frameManager.networkManager.authenticate(credentials)
592
-
593
- async def setExtraHTTPHeaders(self, headers: Dict[str, str]) -> None:
594
- """Set extra HTTP headers.
595
-
596
- The extra HTTP headers will be sent with every request the page
597
- initiates.
598
-
599
- .. note::
600
- ``page.setExtraHTTPHeaders`` does not guarantee the order of
601
- headers in the outgoing requests.
602
-
603
- :arg Dict headers: A dictionary containing additional http headers to
604
- be sent with every requests. All header values must
605
- be string.
606
- """
607
- return await self._frameManager.networkManager.setExtraHTTPHeaders(headers)
608
-
609
- async def setUserAgent(self, userAgent: str) -> None:
610
- """Set user agent to use in this page.
611
-
612
- :arg str userAgent: Specific user agent to use in this page
613
- """
614
- return await self._frameManager.networkManager.setUserAgent(userAgent)
615
-
616
- async def metrics(self) -> Dict[str, Any]:
617
- """Get metrics.
618
-
619
- Returns dictionary containing metrics as key/value pairs:
620
-
621
- * ``Timestamp`` (number): The timestamp when the metrics sample was
622
- taken.
623
- * ``Documents`` (int): Number of documents in the page.
624
- * ``Frames`` (int): Number of frames in the page.
625
- * ``JSEventListeners`` (int): Number of events in the page.
626
- * ``Nodes`` (int): Number of DOM nodes in the page.
627
- * ``LayoutCount`` (int): Total number of full partial page layout.
628
- * ``RecalcStyleCount`` (int): Total number of page style
629
- recalculations.
630
- * ``LayoutDuration`` (int): Combined duration of page duration.
631
- * ``RecalcStyleDuration`` (int): Combined duration of all page style
632
- recalculations.
633
- * ``ScriptDuration`` (int): Combined duration of JavaScript
634
- execution.
635
- * ``TaskDuration`` (int): Combined duration of all tasks performed by
636
- the browser.
637
- * ``JSHeapUsedSize`` (float): Used JavaScript heap size.
638
- * ``JSHeapTotalSize`` (float): Total JavaScript heap size.
639
- """
640
- response = await self._client.send('Performance.getMetrics')
641
- return self._buildMetricsObject(response['metrics'])
642
-
643
- def _emitMetrics(self, event: Dict) -> None:
644
- self.emit(
645
- Events.Page.Metrics, {'title': event['title'], 'metrics': self._buildMetricsObject(event['metrics']),}
646
- )
647
-
648
- def _buildMetricsObject(self, metrics: List) -> Dict[str, Any]:
649
- result = {}
650
- for metric in metrics or []:
651
- if metric['name'] in supportedMetrics:
652
- result[metric['name']] = metric['value']
653
- return result
654
-
655
- def _handleException(self, exceptionDetails: Dict) -> None:
656
- message = helpers.getExceptionMessage(exceptionDetails)
657
- self.emit(Events.Page.PageError, PageError(message))
658
-
659
- def _onConsoleAPI(self, event: dict) -> None:
660
- _id = event['executionContextId']
661
- if _id == 0:
662
- # ignore devtools protocol messages
663
- return
664
- context = self._frameManager.executionContextById(_id)
665
- values: List[JSHandle] = []
666
- for arg in event.get('args', []):
667
- values.append(createJSHandle(context, arg))
668
- self._addConsoleMessage(event['type'], values, event['stackTrace'])
669
-
670
- async def _onBindingCalled(self, event: Dict) -> None:
671
- obj = json.loads(event['payload'])
672
- name = obj['name']
673
- seq = obj['seq']
674
- args = obj['args']
675
- try:
676
- func = self._pageBindings[name]
677
- func_res = func(*args)
678
- result = await func_res if inspect.isawaitable(func_res) else func_res
679
- except Exception as e:
680
- result = str(e)
681
-
682
- deliverResult = '''
683
- function deliverResult(name, seq, result) {
684
- window[name]['callbacks'].get(seq).resolve(result);
685
- window[name]['callbacks'].delete(seq);
686
- }
687
- '''
688
- deliverError = '''
689
- function deliverError(name, seq, message) {
690
- const error = new Error(message);
691
- window[name]['callbacks'].get(seq).reject(error);
692
- window[name]['callbacks'].delete(seq);
693
- }
694
- '''
695
- if isinstance(result, Exception):
696
- expression = helpers.evaluationString(deliverError, name, seq, str(result))
697
- else:
698
- expression = helpers.evaluationString(deliverResult, name, seq, result)
699
-
700
- try:
701
- await self._client.send(
702
- 'Runtime.evaluate', {'expression': expression, 'contextId': event['executionContextId']},
703
- )
704
- except Exception as e:
705
- logger.error(f'An exception occurred: {e}')
706
-
707
- def _addConsoleMessage(
708
- self, type_: str, args: List[JSHandle], stackTrace: Protocol.Runtime.StackTrace = None
709
- ) -> None:
710
- if not self.listeners(Events.Page.Console):
711
- for arg in args:
712
- self._client.loop.create_task(arg.dispose())
713
- return
714
-
715
- textTokens = []
716
- for arg in args:
717
- remoteObject = arg._remoteObject
718
- if remoteObject.get('objectId'):
719
- textTokens.append(arg.toString())
720
- else:
721
- textTokens.append(str(helpers.valueFromRemoteObject(remoteObject)))
722
-
723
- if stackTrace and stackTrace['callFrames']:
724
- location = {
725
- 'url': stackTrace['callFrames'][0]['url'],
726
- 'lineNumber': stackTrace['callFrames'][0]['lineNumber'],
727
- 'columnNumber': stackTrace['callFrames'][0]['columnNumber'],
728
- }
729
- else:
730
- location = {}
731
-
732
- message = ConsoleMessage(type_, ' '.join(textTokens), args, location)
733
- self.emit(Events.Page.Console, message)
734
-
735
- def _onDialog(self, event: Any) -> None:
736
- _type = event.get('type')
737
- if _type == 'alert':
738
- dialogType = Dialog.Type.Alert
739
- elif _type == 'confirm':
740
- dialogType = Dialog.Type.Confirm
741
- elif _type == 'prompt':
742
- dialogType = Dialog.Type.Prompt
743
- elif _type == 'beforeunload':
744
- dialogType = Dialog.Type.BeforeUnload
745
- else:
746
- raise PageError(f'Unknown dialog type: {_type}')
747
- dialog = Dialog(self._client, dialogType, event.get('message'), event.get('defaultPrompt'))
748
- self.emit(Events.Page.Dialog, dialog)
749
-
750
- @property
751
- def url(self) -> str:
752
- """Get URL of this page."""
753
- return self.mainFrame.url
754
-
755
- @property
756
- async def content(self) -> str:
757
- """Get the full HTML contents of the page.
758
-
759
- Returns HTML including the doctype.
760
- """
761
- return await self.mainFrame.content
762
-
763
- async def setContent(self, html: str, timeout: float = None, waitUntil: Union[str, List[str]] = None) -> None:
764
- """
765
- Sets the content of the page
766
- :param html: str containing the HTML to set on the page
767
- :param timeout: amount of time, in ms
768
- :param waitUntil:
769
- :return: None
770
- """
771
- await self.mainFrame.setContent(html=html, timeout=timeout, waitUntil=waitUntil)
772
-
773
- async def goto(
774
- self, url: str, referer: str = None, timeout: float = None, waitUntil: WaitTargets = None,
775
- ) -> Optional[Response]:
776
- """Go to the ``url``.
777
-
778
- :arg string url: URL to navigate page to. The url should include
779
- scheme, e.g. ``https://``.
780
-
781
- Available options are:
782
-
783
- * ``timeout`` (int): Maximum navigation time in milliseconds, defaults
784
- to 30 seconds, pass ``0`` to disable timeout. The default value can
785
- be changed by using the :meth:`setDefaultNavigationTimeout` method.
786
- * ``waitUntil`` (str|List[str]): When to consider navigation succeeded,
787
- defaults to ``load``. Given a list of event strings, navigation is
788
- considered to be successful after all events have been fired. Events
789
- can be either:
790
-
791
- * ``load``: when ``load`` event is fired.
792
- * ``domcontentloaded``: when the ``DOMContentLoaded`` event is fired.
793
- * ``networkidle0``: when there are no more than 0 network connections
794
- for at least 500 ms.
795
- * ``networkidle2``: when there are no more than 2 network connections
796
- for at least 500 ms.
797
-
798
- The ``Page.goto`` will raise errors if:
799
-
800
- * there's an SSL error (e.g. in case of self-signed certificates)
801
- * target URL is invalid
802
- * the ``timeout`` is exceeded during navigation
803
- * then main resource failed to load
804
-
805
- .. note::
806
- :meth:`goto` either raise error or return a main resource response.
807
- The only exceptions are navigation to ``about:blank`` or navigation
808
- to the same URL with a different hash, which would succeed and
809
- return ``None``.
810
-
811
- .. note::
812
- Headless mode doesn't support navigation to a PDF document.
813
- """
814
- return await self.mainFrame.goto(url=url, referer=referer, timeout=timeout, waitUntil=waitUntil,)
815
-
816
- async def reload(self, timeout: float = None, waitUntil: Union[str, List[str]] = None,) -> Optional[Response]:
817
- return (
818
- await asyncio.gather(
819
- self.waitForNavigation(timeout=timeout, waitUntil=waitUntil), self._client.send('Page.reload')
820
- )
821
- )[0]
822
-
823
- async def waitForNavigation(self, timeout: float = None, waitUntil: WaitTargets = None,) -> Optional[Response]:
824
- """Wait for navigation.
825
-
826
- Available options are same as :meth:`goto` method.
827
-
828
- This returns :class:`~pyppeteer.network_manager.Response` when the page
829
- navigates to a new URL or reloads. It is useful for when you run code
830
- which will indirectly cause the page to navigate. In case of navigation
831
- to a different anchor or navigation due to
832
- `History API <https://developer.mozilla.org/en-US/docs/Web/API/History_API>`_
833
- usage, the navigation will return ``None``.
834
-
835
- Consider this example:
836
-
837
- .. code::
838
-
839
- navigationPromise = async.ensure_future(page.waitForNavigation())
840
- await page.click('a.my-link') # indirectly cause a navigation
841
- await navigationPromise # wait until navigation finishes
842
-
843
- or,
844
-
845
- .. code::
846
-
847
- await asyncio.wait([
848
- page.click('a.my-link'),
849
- page.waitForNavigation(),
850
- ])
851
-
852
- .. note::
853
- Usage of the History API to change the URL is considered a
854
- navigation.
855
- """
856
- return await self.mainFrame.waitForNavigation(timeout=timeout, waitUntil=waitUntil)
857
-
858
- def _sessionClosePromise(self) -> Awaitable[None]:
859
- if not self._disconnectPromise:
860
- self._disconnectPromise = self.loop.create_future()
861
- self._client.once(
862
- Events.CDPSession.Disconnected,
863
- lambda: self._disconnectPromise.set_exception(PageError('Target Closed')),
864
- )
865
- return self._disconnectPromise
866
-
867
- async def waitForRequest(
868
- self, urlOrPredicate: Union[str, Callable[[Request], bool]], timeout: float = None
869
- ) -> Request:
870
- """Wait for request.
871
-
872
- :arg urlOrPredicate: A URL or function to wait for.
873
-
874
- This method accepts below options:
875
-
876
- * ``timeout`` (int|float): Maximum wait time in milliseconds, defaults
877
- to 30 seconds, pass ``0`` to disable the timeout.
878
-
879
- Example:
880
-
881
- .. code::
882
-
883
- firstRequest = await page.waitForRequest('http://example.com/resource')
884
- finalRequest = await page.waitForRequest(lambda req: req.url == 'http://example.com' and req.method == 'GET')
885
- return firstRequest.url
886
- """
887
- if not timeout:
888
- timeout = self._timeoutSettings.timeout
889
-
890
- def predicate(request: Request) -> bool:
891
- if isinstance(urlOrPredicate, str):
892
- return urlOrPredicate == request.url
893
- if callable(urlOrPredicate):
894
- return bool(urlOrPredicate(request))
895
- return False
896
-
897
- return await helpers.waitForEvent(
898
- self._frameManager.networkManager, Events.NetworkManager.Request, predicate, timeout, self._client.loop,
899
- )
900
-
901
- async def waitForResponse(
902
- self, urlOrPredicate: Union[str, Callable[[Response], bool]], timeout: float = None
903
- ) -> Response:
904
- """Wait for response.
905
-
906
- :arg urlOrPredicate: A URL or function to wait for.
907
-
908
- This method accepts below options:
909
-
910
- * ``timeout`` (int|float): Maximum wait time in milliseconds, defaults
911
- to 30_000 ms, pass ``0`` to disable the timeout.
912
-
913
- Example:
914
-
915
- .. code::
916
-
917
- firstResponse = await page.waitForResponse('http://example.com/resource')
918
- finalResponse = await page.waitForResponse(lambda res: res.url == 'http://example.com' and res.status == 200)
919
- return finalResponse.ok
920
- """
921
- if not timeout:
922
- timeout = self._timeoutSettings.timeout
923
-
924
- def predicate(response: Response) -> bool:
925
- if isinstance(urlOrPredicate, str):
926
- return urlOrPredicate == response.url
927
- if callable(urlOrPredicate):
928
- return bool(urlOrPredicate(response))
929
- return False
930
-
931
- return await helpers.waitForEvent(
932
- self._frameManager.networkManager, Events.NetworkManager.Response, predicate, timeout, self._client.loop,
933
- )
934
-
935
- async def goBack(self, timeout: float = None, waitUntil: Union[str, List[str]] = None,) -> Optional[Response]:
936
- """Navigate to the previous page in history.
937
-
938
- Available options are same as :meth:`goto` method.
939
-
940
- If cannot go back, return ``None``.
941
- """
942
- return await self._go(-1, timeout=timeout, waitUntil=waitUntil)
943
-
944
- async def goForward(self, timeout: float = None, waitUntil: Union[str, List[str]] = None,) -> Optional[Response]:
945
- """Navigate to the next page in history.
946
-
947
- Available options are same as :meth:`goto` method.
948
-
949
- If cannot go forward, return ``None``.
950
- """
951
- return await self._go(+1, timeout=timeout, waitUntil=waitUntil)
952
-
953
- async def _go(
954
- self, delta: int, timeout: float = None, waitUntil: Union[str, List[str]] = None,
955
- ) -> Optional[Response]:
956
- history = await self._client.send('Page.getNavigationHistory')
957
- try:
958
- entry = history['entries'][history['currentIndex'] + delta]
959
- except IndexError:
960
- return None
961
- return (
962
- await asyncio.gather(
963
- self.waitForNavigation(timeout=timeout, waitUntil=waitUntil),
964
- self._client.send('Page.navigateToHistoryEntry', {'entryId': entry.get('id')}),
965
- )
966
- )[0]
967
-
968
- async def bringToFront(self) -> None:
969
- """Bring page to front (activate tab)."""
970
- await self._client.send('Page.bringToFront')
971
-
972
- async def emulate(self, viewport: Protocol.Page.Viewport, userAgent: str) -> None:
973
- """Emulate given device metrics and user agent.
974
-
975
- This method is a shortcut for calling two methods:
976
-
977
- * :meth:`setUserAgent`
978
- * :meth:`setViewport`
979
-
980
- ``options`` is a dictionary containing these fields:
981
-
982
- * ``viewport`` (dict)
983
-
984
- * ``width`` (int): page width in pixels.
985
- * ``height`` (int): page width in pixels.
986
- * ``deviceScaleFactor`` (float): Specify device scale factor (can be
987
- thought as dpr). Defaults to 1.
988
- * ``isMobile`` (bool): Whether the ``meta viewport`` tag is taken
989
- into account. Defaults to ``False``.
990
- * ``hasTouch`` (bool): Specifies if viewport supports touch events.
991
- Defaults to ``False``.
992
- * ``isLandscape`` (bool): Specifies if viewport is in landscape mode.
993
- Defaults to ``False``.
994
-
995
- * ``userAgent`` (str): user agent string.
996
- """
997
- await asyncio.gather(self.setViewport(viewport), self.setUserAgent(userAgent))
998
-
999
- async def setJavaScriptEnabled(self, enabled: bool) -> None:
1000
- """Set JavaScript enable/disable."""
1001
- if self._javascriptEnabled == enabled:
1002
- return
1003
- self._javascriptEnabled = enabled
1004
- await self._client.send('Emulation.setScriptExecutionDisabled', {'value': not enabled,})
1005
-
1006
- async def setBypassCSP(self, enabled: bool) -> None:
1007
- """Toggles bypassing page's Content-Security-Policy.
1008
-
1009
- .. note::
1010
- CSP bypassing happens at the moment of CSP initialization rather
1011
- then evaluation. Usually this means that ``page.setBypassCSP``
1012
- should be called before navigating to the domain.
1013
- """
1014
- await self._client.send('Page.setBypassCSP', {'enabled': enabled})
1015
-
1016
- async def emulateMedia(self, mediaType: str = None) -> None:
1017
- """Deprecated alias for ``emulateMediaType()``"""
1018
- warnings.warn(
1019
- 'Deprecated: this method is kept for backwards compatibility, '
1020
- 'but may be removed in a future version. '
1021
- 'Use `emulateMediaType(mediaType)` instead',
1022
- category=DeprecationWarning,
1023
- )
1024
- await self.emulateMediaType(mediaType)
1025
-
1026
- async def emulateMediaType(self, mediaType: str = None) -> None:
1027
- """Emulate css media type of the page.
1028
-
1029
- :arg str mediaType: Changes the CSS media type of the page. The only
1030
- allowed values are ``'screen'``, ``'print'``, and
1031
- ``None``. Passing ``None`` disables media
1032
- emulation.
1033
- """
1034
- if mediaType not in ['screen', 'print', None, '']:
1035
- raise ValueError(f'Unsupported media type: {mediaType}')
1036
- await self._client.send('Emulation.setEmulatedMedia', {'media': mediaType or '',})
1037
-
1038
- async def emulateMediaFeatures(
1039
- self, features: List[Dict[Literal['prefers-colors-scheme', 'prefers-reduced-motion'], str]] = None
1040
- ) -> None:
1041
- if not features:
1042
- await self._client.send('Emulation.setEmulatedMedia', {'features': None})
1043
- if isinstance(features, list):
1044
- for feature in features:
1045
- if not re.match(r'prefers-(?:color-scheme|reduced-motion)', feature.get('name', '')):
1046
- raise BrowserError(f'Unsupported media feature: {feature}')
1047
- await self._client.send('Emulation.setEmulatedMedia', {'features': features})
1048
-
1049
- async def emulateTimezone(self, timezoneId: str) -> None:
1050
- try:
1051
- await self._client.send('Emulation.setTimezoneOverride', {'timezoneId': timezoneId})
1052
- except Exception as e:
1053
- msg = e.args[0]
1054
- if 'Invalid timezone' in msg:
1055
- raise PageError(f'Invalid timezone ID: {timezoneId}')
1056
- raise e
1057
-
1058
- async def setViewport(self, viewport: Protocol.Page.Viewport) -> None:
1059
- """Set viewport.
1060
-
1061
- Available options are:
1062
- * ``width`` (int): page width in pixel.
1063
- * ``height`` (int): page height in pixel.
1064
- * ``deviceScaleFactor`` (float): Default to 1.0.
1065
- * ``isMobile`` (bool): Default to ``False``.
1066
- * ``hasTouch`` (bool): Default to ``False``.
1067
- * ``isLandscape`` (bool): Default to ``False``.
1068
- """
1069
- needsReload = await self._emulationManager.emulateViewport(viewport)
1070
- self._viewport = viewport
1071
- if needsReload:
1072
- await self.reload()
1073
-
1074
- @property
1075
- def viewport(self) -> Optional[Protocol.Page.Viewport]:
1076
- """Get viewport as a dictionary or None.
1077
-
1078
- Fields of returned dictionary is same as :meth:`setViewport`.
1079
- """
1080
- return self._viewport
1081
-
1082
- async def evaluate(self, pageFunction: str, *args: JSFunctionArg) -> Any:
1083
- """Execute js-function or js-expression on browser and get result.
1084
-
1085
- :arg str pageFunction: String of js-function/expression to be executed
1086
- on the browser.
1087
-
1088
- """
1089
- return await self.mainFrame.evaluate(pageFunction, *args)
1090
-
1091
- async def evaluateOnNewDocument(self, pageFunction: str, *args: str) -> None:
1092
- """Add a JavaScript function to the document.
1093
-
1094
- This function would be invoked in one of the following scenarios:
1095
-
1096
- * whenever the page is navigated
1097
- * whenever the child frame is attached or navigated. In this case, the
1098
- function is invoked in the context of the newly attached frame.
1099
- """
1100
- source = helpers.evaluationString(pageFunction, *args)
1101
- await self._client.send('Page.addScriptToEvaluateOnNewDocument', {'source': source,})
1102
-
1103
- async def setCacheEnabled(self, enabled: bool = True) -> None:
1104
- """Enable/Disable cache for each request.
1105
-
1106
- By default, caching is enabled.
1107
- """
1108
- await self._frameManager.networkManager.setCacheEnabled(enabled)
1109
-
1110
- async def screenshot(
1111
- self,
1112
- path: Optional[Union[str, Path]] = None,
1113
- type_: str = 'png', # png or jpeg
1114
- quality: int = None, # 0 to 100
1115
- fullPage: bool = False,
1116
- clip: Optional[ScreenshotClip] = None, # x, y, width, height
1117
- omitBackground: bool = False,
1118
- encoding: str = 'binary',
1119
- ) -> Union[bytes, str]:
1120
- """Take a screen shot.
1121
-
1122
- The following options are available:
1123
-
1124
- * ``path`` (str): The file path to save the image to. The screenshot
1125
- type will be inferred from the file extension.
1126
- * ``type`` (str): Specify screenshot type, can be either ``jpeg`` or
1127
- ``png``. Defaults to ``png``.
1128
- * ``quality`` (int): The quality of the image, between 0-100. Not
1129
- applicable to ``png`` image.
1130
- * ``fullPage`` (bool): When true, take a screenshot of the full
1131
- scrollable page. Defaults to ``False``.
1132
- * ``clip`` (dict): An object which specifies clipping region of the
1133
- page. This option should have the following fields:
1134
-
1135
- * ``x`` (int): x-coordinate of top-left corner of clip area.
1136
- * ``y`` (int): y-coordinate of top-left corner of clip area.
1137
- * ``width`` (int): width of clipping area.
1138
- * ``height`` (int): height of clipping area.
1139
-
1140
- * ``omitBackground`` (bool): Hide default white background and allow
1141
- capturing screenshot with transparency.
1142
- * ``encoding`` (str): The encoding of the image, can be either
1143
- ``'base64'`` or ``'binary'``. Defaults to ``'binary'``.
1144
- """
1145
- if type_ not in ['png', 'jpeg']:
1146
- raise ValueError(f'Unknown type value: {type_}')
1147
- if path:
1148
- mimeType, _ = mimetypes.guess_type(str(path))
1149
- if mimeType == 'image/png':
1150
- type_ = 'png'
1151
- elif mimeType == 'image/jpeg':
1152
- type_ = 'jpeg'
1153
- else:
1154
- raise ValueError(f'Unsupported screenshot mime type: {mimeType}')
1155
- if quality:
1156
- if type_ != 'jpeg':
1157
- raise ValueError(f'Screenshot quality is unsupported for {type_} screenshot')
1158
- if not 0 < quality <= 100:
1159
- raise ValueError('Expected screenshot quality to be between 0 and 100 (inclusive)')
1160
- if clip:
1161
- if fullPage:
1162
- raise ValueError('screenshot clip and fullPage options are exclusive')
1163
- if clip['width'] == 0:
1164
- raise ValueError('screenshot clip width cannot be 0')
1165
- if clip['height'] == 0:
1166
- raise ValueError('screenshot clip height cannot be 0')
1167
-
1168
- return await self._screenshotTaskQueue.post_task(
1169
- self._screenshotTask(
1170
- format=type_,
1171
- omitBackground=omitBackground,
1172
- quality=quality,
1173
- clip=clip,
1174
- encoding=encoding,
1175
- fullPage=fullPage,
1176
- path=path,
1177
- )
1178
- )
1179
-
1180
- async def _screenshotTask(
1181
- self,
1182
- format: str, # png or jpeg
1183
- omitBackground: bool,
1184
- quality: Optional[int], # 0 to 100
1185
- clip: Optional[ScreenshotClip],
1186
- encoding: str,
1187
- fullPage: bool,
1188
- path: Optional[Union[str, Path]],
1189
- ) -> Union[bytes, str]:
1190
- await self._client.send('Target.activateTarget', {'targetId': self._target._targetId,})
1191
- if clip:
1192
- x = clip['x']
1193
- y = clip['y']
1194
- clip = {
1195
- 'x': round(x),
1196
- 'y': round(y),
1197
- 'width': round(clip['width'] + clip['x'] - x),
1198
- 'height': round(clip['height'] + clip['y'] - y),
1199
- 'scale': clip.get('scale', 1),
1200
- }
1201
-
1202
- if fullPage:
1203
- metrics = await self._client.send('Page.getLayoutMetrics')
1204
- width = math.ceil(metrics['contentSize']['width'])
1205
- height = math.ceil(metrics['contentSize']['height'])
1206
-
1207
- # Overwrite clip for full page at all times.
1208
- clip = {'x': 0, 'y': 0, 'width': width, 'height': height, 'scale': 1}
1209
- if self._viewport is not None:
1210
- mobile = self._viewport.get('isMobile', False)
1211
- deviceScaleFactor = self._viewport.get('deviceScaleFactor', 1)
1212
- landscape = self._viewport.get('isLandscape', False)
1213
- else:
1214
- mobile = False
1215
- deviceScaleFactor = 1
1216
- landscape = False
1217
-
1218
- if landscape:
1219
- screenOrientation = {'angle': 90, 'type': 'landscapePrimary'}
1220
- else:
1221
- screenOrientation = {'angle': 0, 'type': 'portraitPrimary'}
1222
- await self._client.send(
1223
- 'Emulation.setDeviceMetricsOverride',
1224
- {
1225
- 'mobile': mobile,
1226
- 'width': width,
1227
- 'height': height,
1228
- 'deviceScaleFactor': deviceScaleFactor,
1229
- 'screenOrientation': screenOrientation,
1230
- },
1231
- )
1232
-
1233
- shouldSetDefaultBackground = omitBackground and format == 'png'
1234
- if shouldSetDefaultBackground:
1235
- await self._client.send(
1236
- 'Emulation.setDefaultBackgroundColorOverride', {'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0}},
1237
- )
1238
- result = await self._client.send('Page.captureScreenshot', {'format': format, 'quality': quality, 'clip': clip})
1239
- if shouldSetDefaultBackground:
1240
- await self._client.send('Emulation.setDefaultBackgroundColorOverride')
1241
-
1242
- if fullPage and self._viewport is not None:
1243
- await self.setViewport(self._viewport)
1244
-
1245
- if encoding == 'base64':
1246
- buffer = result.get('data', b'')
1247
- else:
1248
- buffer = base64.b64decode(result.get('data', b''))
1249
- if path:
1250
- with open(path, 'wb') as f:
1251
- f.write(buffer)
1252
- return buffer
1253
-
1254
- async def pdf(
1255
- self,
1256
- scale: float = 1,
1257
- displayHeaderFooter: bool = False,
1258
- headerTemplate: str = '',
1259
- footerTemplate: str = '',
1260
- printBackground: bool = False,
1261
- landscape: bool = False,
1262
- pageRanges: str = '',
1263
- format: str = None,
1264
- width: float = None,
1265
- height: float = None,
1266
- preferCSSPageSize: bool = False,
1267
- margin: Dict[str, float] = None,
1268
- path: Union[Path, str] = None,
1269
- ) -> bytes:
1270
- """Generate a pdf of the page.
1271
-
1272
- Options:
1273
-
1274
- * ``path`` (str): The file path to save the PDF.
1275
- * ``scale`` (float): Scale of the webpage rendering, defaults to ``1``.
1276
- * ``displayHeaderFooter`` (bool): Display header and footer.
1277
- Defaults to ``False``.
1278
- * ``headerTemplate`` (str): HTML template for the print header. Should
1279
- be valid HTML markup with following classes.
1280
-
1281
- * ``date``: formatted print date
1282
- * ``title``: document title
1283
- * ``url``: document location
1284
- * ``pageNumber``: current page number
1285
- * ``totalPages``: total pages in the document
1286
-
1287
- * ``footerTemplate`` (str): HTML template for the print footer. Should
1288
- use the same template as ``headerTemplate``.
1289
- * ``printBackground`` (bool): Print background graphics. Defaults to
1290
- ``False``.
1291
- * ``landscape`` (bool): Paper orientation. Defaults to ``False``.
1292
- * ``pageRanges`` (string): Paper ranges to print, e.g., '1-5,8,11-13'.
1293
- Defaults to empty string, which means all pages.
1294
- * ``format`` (str): Paper format. If set, takes priority over
1295
- ``width`` or ``height``. Defaults to ``Letter``.
1296
- * ``width`` (str): Paper width, accepts values labeled with units.
1297
- * ``height`` (str): Paper height, accepts values labeled with units.
1298
- * ``margin`` (dict): Paper margins, defaults to ``None``.
1299
-
1300
- * ``top`` (str): Top margin, accepts values labeled with units.
1301
- * ``right`` (str): Right margin, accepts values labeled with units.
1302
- * ``bottom`` (str): Bottom margin, accepts values labeled with units.
1303
- * ``left`` (str): Left margin, accepts values labeled with units.
1304
-
1305
- * ``preferCSSPageSize``: Give any CSS ``@page`` size declared in the
1306
- page priority over what is declared in ``width`` and ``height`` or
1307
- ``format`` options. Defaults to ``False``, which will scale the
1308
- content to fit the paper size.
1309
-
1310
- :return: Return generated PDF ``bytes`` object.
1311
-
1312
- .. note::
1313
- Generating a pdf is currently only supported in headless mode.
1314
-
1315
- :meth:`pdf` generates a pdf of the page with ``print`` css media. To
1316
- generate a pdf with ``screen`` media, call
1317
- ``page.emulateMedia('screen')`` before calling :meth:`pdf`.
1318
-
1319
- .. note::
1320
- By default, :meth:`pdf` generates a pdf with modified colors for
1321
- printing. Use the ``--webkit-print-color-adjust`` property to force
1322
- rendering of exact colors.
1323
-
1324
- .. code::
1325
-
1326
- await page.emulateMedia('screen')
1327
- await page.pdf({'path': 'page.pdf'})
1328
-
1329
- The ``width``, ``height``, and ``margin`` options accept values labeled
1330
- with units. Unlabeled values are treated as pixels.
1331
-
1332
- A few examples:
1333
-
1334
- - ``page.pdf({'width': 100})``: prints with width set to 100 pixels.
1335
- - ``page.pdf({'width': '100px'})``: prints with width set to 100 pixels.
1336
- - ``page.pdf({'width': '10cm'})``: prints with width set to 100 centimeters.
1337
-
1338
- All available units are:
1339
-
1340
- - ``px``: pixel
1341
- - ``in``: inch
1342
- - ``cm``: centimeter
1343
- - ``mm``: millimeter
1344
-
1345
- The format options are:
1346
-
1347
- - ``Letter``: 8.5in x 11in
1348
- - ``Legal``: 8.5in x 14in
1349
- - ``Tabloid``: 11in x 17in
1350
- - ``Ledger``: 17in x 11in
1351
- - ``A0``: 33.1in x 46.8in
1352
- - ``A1``: 23.4in x 33.1in
1353
- - ``A2``: 16.5in x 23.4in
1354
- - ``A3``: 11.7in x 16.5in
1355
- - ``A4``: 8.27in x 11.7in
1356
- - ``A5``: 5.83in x 8.27in
1357
- - ``A6``: 4.13in x 5.83in
1358
-
1359
- .. note::
1360
- ``headerTemplate`` and ``footerTemplate`` markup have the following
1361
- limitations:
1362
-
1363
- 1. Script tags inside templates are not evaluated.
1364
- 2. Page styles are not visible inside templates.
1365
- """
1366
- paperWidth: Optional[float] = 8.5
1367
- paperHeight: Optional[float] = 11.0
1368
- if format:
1369
- fmt = Page.PaperFormats.get(format.lower())
1370
- if not fmt:
1371
- raise ValueError(f'Unknown paper format: {format}')
1372
- paperWidth = fmt['width']
1373
- paperHeight = fmt['height']
1374
- else:
1375
- paperWidth = convertPrintParameterToInches(width or paperWidth) # type: ignore
1376
- paperHeight = convertPrintParameterToInches(height or paperHeight) # type: ignore
1377
-
1378
- margin = margin or {}
1379
- marginTop = convertPrintParameterToInches(margin.get('top')) or 0
1380
- marginLeft = convertPrintParameterToInches(margin.get('left')) or 0
1381
- marginBottom = convertPrintParameterToInches(margin.get('bottom')) or 0
1382
- marginRight = convertPrintParameterToInches(margin.get('right')) or 0
1383
-
1384
- result = await self._client.send(
1385
- 'Page.printToPDF',
1386
- {
1387
- 'transferMode': 'ReturnAsStream',
1388
- 'landscape': landscape,
1389
- 'displayHeaderFooter': displayHeaderFooter,
1390
- 'headerTemplate': headerTemplate,
1391
- 'footerTemplate': footerTemplate,
1392
- 'printBackground': printBackground,
1393
- 'scale': scale,
1394
- 'paperWidth': paperWidth,
1395
- 'paperHeight': paperHeight,
1396
- 'marginTop': marginTop,
1397
- 'marginBottom': marginBottom,
1398
- 'marginLeft': marginLeft,
1399
- 'marginRight': marginRight,
1400
- 'pageRanges': pageRanges,
1401
- 'preferCSSPageSize': preferCSSPageSize,
1402
- },
1403
- )
1404
- buffer = base64.b64decode(result.get('data', b''))
1405
- if path:
1406
- with open(path, 'wb') as f:
1407
- f.write(buffer)
1408
- return buffer
1409
-
1410
- @property
1411
- async def title(self) -> str:
1412
- """Get page's title."""
1413
- return await self.mainFrame.title
1414
-
1415
- async def close(self, runBeforeUnload: bool = False) -> None:
1416
- """Close this page.
1417
-
1418
- Available options:
1419
-
1420
- * ``runBeforeUnload`` (bool): Defaults to ``False``. Whether to run the
1421
- `before unload <https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload>`_
1422
- page handlers.
1423
-
1424
- By defaults, :meth:`close` **does not** run beforeunload handlers.
1425
-
1426
- .. note::
1427
- If ``runBeforeUnload`` is passed as ``True``, a ``beforeunload``
1428
- dialog might be summoned and should be handled manually via page's
1429
- ``dialog`` event.
1430
- """
1431
- conn = self._client._connection
1432
- if conn is None:
1433
- raise PageError('Protocol Error: Connection Closed. Most likely the page has been closed.')
1434
- if runBeforeUnload:
1435
- await self._client.send('Page.close')
1436
- else:
1437
- await conn.send('Target.closeTarget', {'targetId': self._target._targetId})
1438
- await self._target._isClosedPromise
1439
-
1440
- @property
1441
- def isClosed(self) -> bool:
1442
- """Indicate that the page has been closed."""
1443
- return self._closed
1444
-
1445
- @property
1446
- def mouse(self) -> Mouse:
1447
- """Get :class:`~pyppeteer.input.Mouse` object."""
1448
- return self._mouse
1449
-
1450
- async def click(self, selector: str, delay: float = 0, button: MouseButton = 'left', clickCount: int = 1,) -> None:
1451
- """Click element which matches ``selector``.
1452
-
1453
- This method fetches an element with ``selector``, scrolls it into view
1454
- if needed, and then uses :attr:`mouse` to click in the center of the
1455
- element. If there's no element matching ``selector``, the method raises
1456
- ``PageError``.
1457
-
1458
- Available options are:
1459
-
1460
- * ``button`` (str): ``left``, ``right``, or ``middle``, defaults to
1461
- ``left``.
1462
- * ``clickCount`` (int): defaults to 1.
1463
- * ``delay`` (int|float): Time to wait between ``mousedown`` and
1464
- ``mouseup`` in milliseconds. defaults to 0.
1465
-
1466
- .. note:: If this method triggers a navigation event and there's a
1467
- separate :meth:`waitForNavigation`, you may end up with a race
1468
- condition that yields unexpected results. The correct pattern for
1469
- click and wait for navigation is the following::
1470
-
1471
- await asyncio.gather(
1472
- page.waitForNavigation(waitOptions),
1473
- page.click(selector, clickOptions),
1474
- )
1475
- """
1476
- await self.mainFrame.click(
1477
- selector=selector, delay=delay, button=button, clickCount=clickCount,
1478
- )
1479
-
1480
- async def focus(self, selector: str) -> None:
1481
- """Focus the element which matches ``selector``.
1482
-
1483
- If no element matched the ``selector``, raise ``PageError``.
1484
- """
1485
- await self.mainFrame.focus(selector)
1486
-
1487
- async def hover(self, selector: str) -> None:
1488
- """Mouse hover the element which matches ``selector``.
1489
-
1490
- If no element matched the ``selector``, raise ``PageError``.
1491
- """
1492
- await self.mainFrame.hover(selector)
1493
-
1494
- async def select(self, selector: str, *values: str) -> List[str]:
1495
- """Select options and return selected values.
1496
-
1497
- If no element matched the ``selector``, raise ``ElementHandleError``.
1498
- """
1499
- return await self.mainFrame.select(selector, *values)
1500
-
1501
- async def tap(self, selector: str) -> None:
1502
- """Tap the element which matches the ``selector``.
1503
-
1504
- :arg str selector: A selector to search element to touch.
1505
- """
1506
- await self.mainFrame.tap(selector)
1507
-
1508
- async def type(self, selector: str, text: str, **kwargs: Any) -> None:
1509
- """Type ``text`` on the element which matches ``selector``.
1510
-
1511
- If no element matched the ``selector``, raise ``PageError``.
1512
-
1513
- Details see :meth:`pyppeteer.input.Keyboard.type`.
1514
- """
1515
- return await self.mainFrame.type(selector, text, **kwargs)
1516
-
1517
- def waitFor(self, selectorOrFunctionOrTimeout: Union[str, int, float], *args: JSFunctionArg, **kwargs) -> Awaitable:
1518
- """Wait for function, timeout, or element which matches on page.
1519
-
1520
- This method behaves differently with respect to the first argument:
1521
-
1522
- * If ``selectorOrFunctionOrTimeout`` is number (int or float), then it
1523
- is treated as a timeout in milliseconds and this returns future which
1524
- will be done after the timeout.
1525
- * If ``selectorOrFunctionOrTimeout`` is a string of JavaScript
1526
- function, this method is a shortcut to :meth:`waitForFunction`.
1527
- * If ``selectorOrFunctionOrTimeout`` is a selector string or xpath
1528
- string, this method is a shortcut to :meth:`waitForSelector` or
1529
- :meth:`waitForXPath`. If the string starts with ``//``, the string is
1530
- treated as xpath.
1531
-
1532
- Pyppeteer tries to automatically detect function or selector, but
1533
- sometimes miss-detects. If not work as you expected, use
1534
- :meth:`waitForFunction` or :meth:`waitForSelector` directly.
1535
-
1536
- :arg selectorOrFunctionOrTimeout: A selector, xpath, or function
1537
- string, or timeout (milliseconds).
1538
- :arg Any args: Arguments to pass the function.
1539
- :return: Return awaitable object which resolves to a JSHandle of the
1540
- success value.
1541
-
1542
- Available options: see :meth:`waitForFunction` or
1543
- :meth:`waitForSelector`
1544
- """
1545
- return self.mainFrame.waitFor(selectorOrFunctionOrTimeout, *args, **kwargs)
1546
-
1547
- def waitForSelector(self, selector: str, **kwargs: Any) -> Awaitable:
1548
- """Wait until element which matches ``selector`` appears on page.
1549
-
1550
- Wait for the ``selector`` to appear in page. If at the moment of
1551
- calling the method the ``selector`` already exists, the method will
1552
- return immediately. If the selector doesn't appear after the
1553
- ``timeout`` milliseconds of waiting, the function will raise error.
1554
-
1555
- :arg str selector: A selector of an element to wait for.
1556
- :return: Return awaitable object which resolves when element specified
1557
- by selector string is added to DOM.
1558
-
1559
- This method accepts the following options:
1560
-
1561
- * ``visible`` (bool): Wait for element to be present in DOM and to be
1562
- visible; i.e. to not have ``display: none`` or ``visibility: hidden``
1563
- CSS properties. Defaults to ``False``.
1564
- * ``hidden`` (bool): Wait for element to not be found in the DOM or to
1565
- be hidden, i.e. have ``display: none`` or ``visibility: hidden`` CSS
1566
- properties. Defaults to ``False``.
1567
- * ``timeout`` (int|float): Maximum time to wait for in milliseconds.
1568
- Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.
1569
- """
1570
- return self.mainFrame.waitForSelector(selector, **kwargs)
1571
-
1572
- def waitForXPath(
1573
- self, xpath: str, visible: bool = False, hidden: bool = False, timeout: Optional[int] = None
1574
- ) -> Awaitable:
1575
- """Wait until element which matches ``xpath`` appears on page.
1576
-
1577
- Wait for the ``xpath`` to appear in page. If the moment of calling the
1578
- method the ``xpath`` already exists, the method will return
1579
- immediately. If the xpath doesn't appear after ``timeout`` milliseconds
1580
- of waiting, the function will raise exception.
1581
-
1582
-
1583
- :arg str xpath: A [xpath] of an element to wait for.
1584
- :return: Return awaitable object which resolves when element specified
1585
- by xpath string is added to DOM.
1586
-
1587
- Available options are:
1588
-
1589
- * ``visible`` (bool): wait for element to be present in DOM and to be
1590
- visible, i.e. to not have ``display: none`` or ``visibility: hidden``
1591
- CSS properties. Defaults to ``False``.
1592
- * ``hidden`` (bool): wait for element to not be found in the DOM or to
1593
- be hidden, i.e. have ``display: none`` or ``visibility: hidden`` CSS
1594
- properties. Defaults to ``False``.
1595
- * ``timeout`` (int|float): maximum time to wait for in milliseconds.
1596
- Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.
1597
- """
1598
- return self.mainFrame.waitForXPath(xpath, visible=visible, hidden=hidden, timeout=timeout)
1599
-
1600
- async def waitForFunction(
1601
- self, pageFunction: str, *args: JSFunctionArg, polling: str = 'raf', timeout: Optional[float] = None,
1602
- ) -> Awaitable[JSHandle]:
1603
- """Wait until the function completes and returns a truthy value.
1604
-
1605
- :arg Any args: Arguments to pass to ``pageFunction``.
1606
- :return: Return awaitable object which resolves when the
1607
- ``pageFunction`` returns a truthy value. It resolves to a
1608
- :class:`~pyppeteer.execution_context.JSHandle` of the truthy
1609
- value.
1610
-
1611
- This method accepts the following options:
1612
-
1613
- * ``polling`` (str|number): An interval at which the ``pageFunction``
1614
- is executed, defaults to ``raf``. If ``polling`` is a number, then
1615
- it is treated as an interval in milliseconds at which the function
1616
- would be executed. If ``polling`` is a string, then it can be one of
1617
- the following values:
1618
-
1619
- * ``raf``: to constantly execute ``pageFunction`` in
1620
- ``requestAnimationFrame`` callback. This is the tightest polling
1621
- mode which is suitable to observe styling changes.
1622
- * ``mutation``: to execute ``pageFunction`` on every DOM mutation.
1623
-
1624
- * ``timeout`` (int|float): maximum time to wait for in milliseconds.
1625
- Defaults to 30000 (30 seconds). Pass ``0`` to disable timeout.
1626
- """
1627
- return self.mainFrame.waitForFunction(pageFunction, *args, polling=polling, timeout=timeout, *args)
1628
-
1629
-
1630
- supportedMetrics = (
1631
- 'Timestamp',
1632
- 'Documents',
1633
- 'Frames',
1634
- 'JSEventListeners',
1635
- 'Nodes',
1636
- 'LayoutCount',
1637
- 'RecalcStyleCount',
1638
- 'LayoutDuration',
1639
- 'RecalcStyleDuration',
1640
- 'ScriptDuration',
1641
- 'TaskDuration',
1642
- 'JSHeapUsedSize',
1643
- 'JSHeapTotalSize',
1644
- )
1645
-
1646
- unitToPixels = {'px': 1, 'in': 96, 'cm': 37.8, 'mm': 3.78}
1647
-
1648
-
1649
- def convertPrintParameterToInches(parameter: Optional[Union[int, float, str]]) -> Optional[float]:
1650
- """Convert print parameter to inches."""
1651
- if parameter is None:
1652
- return None
1653
- if isinstance(parameter, (int, float)):
1654
- pixels = parameter
1655
- elif isinstance(parameter, str):
1656
- text = parameter
1657
- unit = text[-2:].lower()
1658
- if unit in unitToPixels:
1659
- valueText = text[:-2]
1660
- else:
1661
- unit = 'px'
1662
- valueText = text
1663
- try:
1664
- value = float(valueText)
1665
- except ValueError:
1666
- raise ValueError('Failed to parse parameter value: ' + text)
1667
- pixels = value * unitToPixels[unit]
1668
- else:
1669
- raise TypeError('page.pdf() Cannot handle parameter type: ' + str(type(parameter)))
1670
- return pixels / 96
1671
-
1672
-
1673
- class ConsoleMessage:
1674
- """Console message class.
1675
-
1676
- ConsoleMessage objects are dispatched by page via the ``console`` event.
1677
- """
1678
-
1679
- def __init__(
1680
- self, type: str, text: str, args: Optional[List[JSHandle]] = None, location: Dict[str, Union[str, int]] = None
1681
- ) -> None:
1682
- self._args = args
1683
- self._type = type
1684
- self._text = text
1685
- self._location = location or {}
1686
-
1687
- @property
1688
- def args(self) -> Optional[List[JSHandle]]:
1689
- return self._args
1690
-
1691
- @property
1692
- def type(self) -> str:
1693
- """Return type of this message."""
1694
- return self._type
1695
-
1696
- @property
1697
- def text(self) -> str:
1698
- """Return text representation of this message."""
1699
- return self._text
1700
-
1701
- @property
1702
- def location(self) -> Dict[str, Any]:
1703
- return self._location
1704
-
1705
-
1706
- class FileChooser:
1707
- def __init__(
1708
- self, client: CDPSession, element: ElementHandle, event: Dict,
1709
- ):
1710
- self._client = client
1711
- self._element = element
1712
- self._multiple = event.get('mode') != 'selectSingle'
1713
- self._handled = False
1714
-
1715
- @property
1716
- def isMultiple(self) -> bool:
1717
- return self._multiple
1718
-
1719
- async def accept(self, filePaths: Sequence[Union[Path, str]]) -> None:
1720
- if self._handled:
1721
- raise ValueError('Cannot accept FileChooser which is already handled!')
1722
- self._handled = True
1723
- await self._element.uploadFile(*filePaths)
1724
-
1725
- async def cancel(self) -> None:
1726
- if self._handled:
1727
- raise ValueError('Cannot cancel Filechooser which is already handled!')
1728
- self._handled = True