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.
- biolib/__init__.py +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /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
|