prettyplay 0.0.0__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.

Potentially problematic release.


This version of prettyplay might be problematic. Click here for more details.

Files changed (58) hide show
  1. prettyplay/.usages/lifecycle.md +50 -0
  2. prettyplay/.usages/steps.md +56 -0
  3. prettyplay/CODEMANIFEST +182 -0
  4. prettyplay/__init__.py +13 -0
  5. prettyplay/cache/.usages/addressing.md +31 -0
  6. prettyplay/cache/.usages/budgets.md +21 -0
  7. prettyplay/cache/.usages/storage.md +32 -0
  8. prettyplay/cache/CODEMANIFEST +152 -0
  9. prettyplay/cache/__init__.py +8 -0
  10. prettyplay/cache/budgets.py +73 -0
  11. prettyplay/cache/models.py +60 -0
  12. prettyplay/cache/store.py +260 -0
  13. prettyplay/cache/text.py +35 -0
  14. prettyplay/config/.usages/configuration.md +87 -0
  15. prettyplay/config/CODEMANIFEST +132 -0
  16. prettyplay/config/__init__.py +6 -0
  17. prettyplay/config/loader.py +192 -0
  18. prettyplay/config/models.py +92 -0
  19. prettyplay/driver/.usages/facade.md +66 -0
  20. prettyplay/driver/CODEMANIFEST +162 -0
  21. prettyplay/driver/__init__.py +6 -0
  22. prettyplay/driver/page.py +350 -0
  23. prettyplay/driver/session.py +288 -0
  24. prettyplay/engine/.usages/generation.md +45 -0
  25. prettyplay/engine/.usages/healing.md +31 -0
  26. prettyplay/engine/CODEMANIFEST +215 -0
  27. prettyplay/engine/__init__.py +8 -0
  28. prettyplay/engine/classification.py +69 -0
  29. prettyplay/engine/execution.py +25 -0
  30. prettyplay/engine/generator.py +318 -0
  31. prettyplay/engine/healer.py +116 -0
  32. prettyplay/engine/text.py +19 -0
  33. prettyplay/executor.py +109 -0
  34. prettyplay/failures/.usages/taxonomy.md +40 -0
  35. prettyplay/failures/CODEMANIFEST +117 -0
  36. prettyplay/failures/__init__.py +17 -0
  37. prettyplay/failures/errors.py +147 -0
  38. prettyplay/llm/.usages/classification.md +25 -0
  39. prettyplay/llm/.usages/providers.md +33 -0
  40. prettyplay/llm/CODEMANIFEST +125 -0
  41. prettyplay/llm/__init__.py +8 -0
  42. prettyplay/llm/_request.py +216 -0
  43. prettyplay/llm/anthropic_provider.py +213 -0
  44. prettyplay/llm/models.py +22 -0
  45. prettyplay/llm/openai_provider.py +187 -0
  46. prettyplay/llm/provider.py +115 -0
  47. prettyplay/reporting/.usages/hooks.md +41 -0
  48. prettyplay/reporting/CODEMANIFEST +79 -0
  49. prettyplay/reporting/__init__.py +6 -0
  50. prettyplay/reporting/hooks.py +37 -0
  51. prettyplay/reporting/reporter.py +70 -0
  52. prettyplay/runtime.py +107 -0
  53. prettyplay/scenario.py +291 -0
  54. prettyplay-0.0.0.dist-info/METADATA +236 -0
  55. prettyplay-0.0.0.dist-info/RECORD +58 -0
  56. prettyplay-0.0.0.dist-info/WHEEL +5 -0
  57. prettyplay-0.0.0.dist-info/licenses/LICENSE +28 -0
  58. prettyplay-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,192 @@
1
+ """Loading of the ``[tool.prettyplay]`` section of pyproject.toml with layered overrides.
2
+
3
+ The pyproject.toml path is either given explicitly or auto-searched upward from
4
+ the current working directory. Environment overrides win over the file
5
+ whenever the variable is set — including when it is set to an empty string:
6
+ ``PRETTYPLAY_<SETTING_UPPER>`` for every setting except the browser
7
+ (``PRETTYPLAY_BROWSER_NAME``) and headless (``PRETTYPLAY_BROWSER_HEADLESS``).
8
+ The removed legacy name ``PRETTYPLAY_BROWSER`` fails loudly before merging.
9
+ Explicitly set programmatic values (``PrettyConfig`` fields passed at
10
+ construction, non-empty for strings) win over the pyproject+env layer.
11
+ LLM API keys are never read here: they come only from the provider clients
12
+ themselves.
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ from pydantic import ValidationError
20
+
21
+ from ..failures.errors import PrettyplayError
22
+ from .models import Config
23
+
24
+ if sys.version_info >= (3, 11):
25
+ import tomllib
26
+ else:
27
+ import tomli as tomllib
28
+
29
+ #: The removed legacy env name; set — the load fails loudly before merging.
30
+ _LEGACY_BROWSER_ENV = "PRETTYPLAY_BROWSER"
31
+
32
+ #: Env variable name of every overridable setting; the two browser entries
33
+ #: keep their historical special names.
34
+ _ENV_NAMES: dict[str, str] = {
35
+ field: f"PRETTYPLAY_{field.upper()}"
36
+ for field in (
37
+ "provider",
38
+ "model",
39
+ "generation_model",
40
+ "classification_model",
41
+ "base_url",
42
+ "cache_root",
43
+ "generation_prompt",
44
+ "browser_endpoint",
45
+ "generation_attempts",
46
+ "healing_attempts",
47
+ "send_screenshots",
48
+ )
49
+ }
50
+ _ENV_NAMES["browser"] = "PRETTYPLAY_BROWSER_NAME"
51
+ _ENV_NAMES["headless"] = "PRETTYPLAY_BROWSER_HEADLESS"
52
+
53
+ #: The allowed-values text of the settings the validation render names.
54
+ _ALLOWED_TEXT: dict[str, str] = {
55
+ "provider": "openai, anthropic",
56
+ "browser": "chromium, firefox, webkit, chrome, msedge",
57
+ "generation_attempts": "a positive integer",
58
+ "healing_attempts": "a positive integer",
59
+ "headless": "a boolean",
60
+ "send_screenshots": "a boolean",
61
+ "model": "a non-empty string",
62
+ "generation_model": "a non-empty string",
63
+ "classification_model": "a non-empty string",
64
+ "base_url": "a non-empty string",
65
+ "cache_root": "a non-empty string",
66
+ "generation_prompt": "a non-empty string",
67
+ "browser_endpoint": "a valid ws/wss URL",
68
+ }
69
+
70
+
71
+ class ConfigurationError(PrettyplayError):
72
+ """An invalid prettyplay configuration: the loaded settings failed validation.
73
+
74
+ Raised by :func:`load_config` with the original pydantic
75
+ ``ValidationError`` chained; catchable with the single library except
76
+ clause. Never carries a verdict — a configuration failure is not a step
77
+ failure.
78
+
79
+ Args:
80
+ message: the rendered actionable text — one line per invalid setting.
81
+ """
82
+
83
+
84
+ def _find_pyproject() -> Path:
85
+ """Return the first existing pyproject.toml upward from the cwd.
86
+
87
+ Returns:
88
+ Absolute path of the found pyproject.toml.
89
+
90
+ Raises:
91
+ FileNotFoundError: when no pyproject.toml exists upward from the cwd.
92
+ """
93
+ for directory in (Path.cwd(), *Path.cwd().parents):
94
+ candidate = directory / "pyproject.toml"
95
+ if candidate.is_file():
96
+ return candidate
97
+
98
+ raise FileNotFoundError("pyproject.toml not found upward from the current directory")
99
+
100
+
101
+ def _collect_env_overrides() -> dict[str, str]:
102
+ """Collect the set environment overrides of all supported settings."""
103
+ overrides: dict[str, str] = {}
104
+
105
+ for field, env_name in _ENV_NAMES.items():
106
+ value = os.environ.get(env_name)
107
+ if value is not None:
108
+ overrides[field] = value
109
+
110
+ return overrides
111
+
112
+
113
+ def _render_validation(error: ValidationError) -> str:
114
+ """Render the pydantic error as actionable per-setting lines.
115
+
116
+ Args:
117
+ error: the pydantic validation failure of the merged settings.
118
+
119
+ Returns:
120
+ One line per invalid setting — the setting name, the received value
121
+ and the allowed values or range — joined with newlines; no pydantic
122
+ internals in the user-visible text.
123
+ """
124
+ lines: list[str] = []
125
+
126
+ for entry in error.errors():
127
+ field = entry["loc"][0]
128
+ received = entry.get("input")
129
+ allowed = _ALLOWED_TEXT.get(str(field), entry["msg"])
130
+ lines.append(f"{field}: received {received!r} — allowed: {allowed}")
131
+
132
+ return "\n".join(lines)
133
+
134
+
135
+ def load_config(pyproject_path: str | None = None, overrides: Config | None = None) -> Config:
136
+ """Load validated settings layered pyproject → env → explicit programmatic values.
137
+
138
+ The section is optional: a missing ``[tool.prettyplay]`` yields defaults.
139
+ Environment variables override the file value whenever they are set, empty
140
+ string included: ``PRETTYPLAY_BROWSER_NAME`` for the browser,
141
+ ``PRETTYPLAY_BROWSER_HEADLESS`` for headless and
142
+ ``PRETTYPLAY_<SETTING_UPPER>`` for every other setting. An empty
143
+ ``cache_root`` resolves to ``<pyproject_dir>/.prettyplay/cache``.
144
+
145
+ The programmatic layer wins last: a field of ``overrides`` participates
146
+ when it was passed at construction (``model_fields_set``) and is non-empty
147
+ for strings — explicitly set values win, untouched model defaults and
148
+ empty strings never overwrite the pyproject+env values.
149
+
150
+ Args:
151
+ pyproject_path: explicit pyproject.toml path; ``None`` auto-searches
152
+ upward from the current working directory.
153
+ overrides: the programmatic layer, typically a ``PrettyConfig`` built
154
+ by the integrator; ``None`` (or an instance with no explicitly set
155
+ fields) returns the validated file layer as is.
156
+
157
+ Returns:
158
+ Validated configuration.
159
+
160
+ Raises:
161
+ FileNotFoundError: when ``pyproject_path`` is ``None`` and no
162
+ pyproject.toml is found upward from the cwd.
163
+ tomllib.TOMLDecodeError: when the file is not valid TOML.
164
+ ConfigurationError: when merged settings fail validation — the
165
+ original ``ValidationError`` chained as the cause.
166
+ """
167
+ path = Path(pyproject_path) if pyproject_path is not None else _find_pyproject()
168
+
169
+ with path.open("rb") as stream:
170
+ data = tomllib.load(stream)
171
+ section = data.get("tool", {}).get("prettyplay", {})
172
+
173
+ if _LEGACY_BROWSER_ENV in os.environ:
174
+ raise ConfigurationError(f"{_LEGACY_BROWSER_ENV} is no longer supported: use PRETTYPLAY_BROWSER_NAME")
175
+
176
+ merged = {**section, **_collect_env_overrides()}
177
+
178
+ if not merged.get("cache_root"):
179
+ merged["cache_root"] = str(path.parent / ".prettyplay" / "cache")
180
+
181
+ try:
182
+ file_config = Config(**merged)
183
+ except ValidationError as error:
184
+ raise ConfigurationError(_render_validation(error)) from error
185
+
186
+ if overrides is None:
187
+ return file_config
188
+
189
+ explicit = overrides.model_fields_set
190
+ update = {name: value for name, value in overrides if name in explicit and (value or not isinstance(value, str))}
191
+
192
+ return file_config.model_copy(update=update)
@@ -0,0 +1,92 @@
1
+ """Validated project settings of prettyplay.
2
+
3
+ The single source of the immutable configuration part. Secrets (LLM API keys)
4
+ never live here: they come only from environment variables.
5
+ """
6
+
7
+ from typing import Literal
8
+ from urllib.parse import urlparse
9
+
10
+ from pydantic import BaseModel, ConfigDict, PositiveInt, field_validator
11
+
12
+
13
+ class Config(BaseModel):
14
+ """Validated project settings loaded from ``[tool.prettyplay]``.
15
+
16
+ Every field has an empty default so the model can be constructed before a
17
+ pyproject.toml section exists; ``load_config`` fills and resolves it.
18
+
19
+ Attributes:
20
+ provider: the LLM provider of the {openai, anthropic} set; default openai.
21
+ browser: browser of the {chromium, firefox, webkit, chrome, msedge} set;
22
+ chrome and msedge launch the locally installed browser through the
23
+ driver channel mechanism; default chromium.
24
+ model: main LLM model name.
25
+ generation_model: optional generation override; empty falls back to ``model``.
26
+ classification_model: optional classification override; empty falls back to ``model``.
27
+ base_url: optional custom LLM API endpoint.
28
+ cache_root: cache root; empty means the default resolved at load.
29
+ generation_prompt: user instructions for generation requests; non-empty
30
+ renders a separate USER INSTRUCTIONS block in generation and
31
+ regeneration requests; empty means no block.
32
+ browser_endpoint: ws endpoint of a remote browser; empty means the local
33
+ launch; on a remote connect headless is ignored.
34
+ generation_attempts: generation attempt budget per step per test; default 3.
35
+ healing_attempts: healing attempt budget per step per test; default 2.
36
+ send_screenshots: whether screenshots are attached to LLM requests.
37
+ headless: run the browser without a visible window; default True.
38
+ """
39
+
40
+ model_config = ConfigDict(kw_only=True)
41
+
42
+ provider: Literal["openai", "anthropic"] = "openai"
43
+ browser: Literal["chromium", "firefox", "webkit", "chrome", "msedge"] = "chromium"
44
+ model: str = ""
45
+ generation_model: str = ""
46
+ classification_model: str = ""
47
+ base_url: str = ""
48
+ cache_root: str = ""
49
+ generation_prompt: str = ""
50
+ browser_endpoint: str = ""
51
+ generation_attempts: PositiveInt = 3
52
+ healing_attempts: PositiveInt = 2
53
+ send_screenshots: bool = False
54
+ headless: bool = True
55
+
56
+ @field_validator("browser_endpoint")
57
+ @classmethod
58
+ def _validate_browser_endpoint(cls, value: str) -> str:
59
+ """Check that a non-empty endpoint is a ws/wss URL.
60
+
61
+ Args:
62
+ value: the raw endpoint setting; empty means the local launch.
63
+
64
+ Returns:
65
+ The unchanged endpoint when empty or a valid ws/wss URL.
66
+
67
+ Raises:
68
+ ValueError: when a non-empty endpoint is not a ws/wss URL.
69
+ """
70
+ if not value:
71
+ return value
72
+
73
+ parsed = urlparse(value)
74
+
75
+ if parsed.scheme not in {"ws", "wss"} or not parsed.netloc:
76
+ raise ValueError("must be a valid ws/wss URL")
77
+
78
+ return value
79
+
80
+ @property
81
+ def effective_generation_model(self) -> str:
82
+ """Return generation_model when non-empty, otherwise model."""
83
+ return self.generation_model if self.generation_model else self.model
84
+
85
+ @property
86
+ def effective_classification_model(self) -> str:
87
+ """Return classification_model when non-empty, otherwise model."""
88
+ return self.classification_model if self.classification_model else self.model
89
+
90
+
91
+ #: The public name of ``Config``: the integrator-facing settings model.
92
+ PrettyConfig = Config
@@ -0,0 +1,66 @@
1
+ # Driver facade
2
+
3
+ Domain: the browser facade of prettyplay. Audience: consumers of the page API — the step generation engine and engineers reading or hand-writing step code.
4
+
5
+ The facade wraps the Playwright sync API. Step code receives a `PageFacade` and works only through it and `LocatorFacade` — never through raw Playwright objects. The method set is a backward-compatibility contract: cached step code keeps working across library upgrades.
6
+
7
+ ## Surface
8
+
9
+ | Call | Purpose |
10
+ |---|---|
11
+ | page.open(url) | navigate and wait for load |
12
+ | page.find_by_role(role, name) | element by aria role and accessible name |
13
+ | page.find_by_label(label) | element by associated label |
14
+ | page.find_by_text(text) | element by visible text |
15
+ | page.find_by_attribute(name, value) | element by attribute value — data-* attributes |
16
+ | page.find_by_css(selector) | element by CSS selector |
17
+ | page.find_by_xpath(xpath) | element by XPath expression |
18
+ | page.aria_snapshot() | accessibility-tree page state |
19
+ | page.screenshot() | full-page PNG bytes |
20
+ | page.url | current URL |
21
+ | page.scroll_to_element(element) | bring an element into the viewport (works inside scrollable ancestors) |
22
+ | page.scroll_down(pixels) | scroll the page down by an amount |
23
+ | page.scroll_up(pixels) | scroll the page up by an amount |
24
+ | page.scroll_to_bottom() | scroll to the end of the page |
25
+ | page.scroll_to_top() | scroll to the start of the page |
26
+ | page.scroll_into_view(element, container) | bring an element into view inside a specific scrollable container |
27
+ | page.scroll_container_down(container, pixels) | scroll a scrollable container down by an amount |
28
+ | page.scroll_container_up(container, pixels) | scroll a scrollable container up by an amount |
29
+ | element.click() | click with auto-wait |
30
+ | element.fill(value) | set input text |
31
+ | element.select_option(value) | choose an option |
32
+ | element.expect_visible() | assert visible |
33
+ | element.expect_text(text) | assert text |
34
+ | element.expect_enabled() | assert enabled |
35
+
36
+ ## Example
37
+
38
+ ```python
39
+ page.open("https://example.com/login")
40
+ page.find_by_label("Username").fill("user")
41
+ page.find_by_label("Password").fill("secret")
42
+ page.find_by_role("button", name="Sign in").click()
43
+ page.find_by_text("Welcome back").expect_visible()
44
+
45
+ # locating by data attributes, CSS and XPath
46
+ page.find_by_attribute("data-test-id", "submit-button").click()
47
+ page.find_by_css("form > button.primary").expect_enabled()
48
+ page.find_by_xpath("//button[@type='submit']").expect_visible()
49
+
50
+ # scroll scenarios
51
+ page.scroll_down(600)
52
+ page.find_by_text("Footer").expect_visible()
53
+
54
+ cards = page.find_by_role("list", name="Recommendations")
55
+ page.scroll_container_down(cards, 400)
56
+ page.find_by_text("Fifth card").expect_visible()
57
+
58
+ snapshot = page.aria_snapshot()
59
+ ```
60
+
61
+ ## Rules
62
+
63
+ - One browser process per test: each test owns its browser through its runtime; contexts stay isolated
64
+ - Every call executes in the library's driver thread and returns when done: driving is strictly sequential, and the calling thread never adopts the Playwright event loop — hand-written step code stays safe in interactive hosts (IPython, Jupyter)
65
+ - Auto-wait everywhere: no time.sleep, no fixed delays in step code — including around scrolls: the scrolled state is awaited through locators and expectations
66
+ - Never put secrets into step actions — step texts and code land in the repository cache
@@ -0,0 +1,162 @@
1
+ Imports:
2
+ - Types:
3
+ - Config
4
+ From: prettyplay/config
5
+
6
+ Usages:
7
+ conventions: .goga/usages/conventions.md
8
+ playwright: .goga/usages/cooks/playwright.md
9
+
10
+ Annotations: |
11
+ Use `conventions` for code writing rules and testing.
12
+ Use `playwright` for the sync API lifecycle, locators, auto-wait, the accessibility snapshot, browser channels, remote connects and the scroll primitives.
13
+
14
+ The driver is Playwright sync-only: the async API is out of scope.
15
+ The whole Playwright session — start, browser, contexts, pages — lives in one dedicated driver thread owned by the library: the sync API parks its private event loop on its starting thread, so the thread executing the steps never holds a running asyncio loop (interactive hosts such as IPython and Jupyter keep working between steps).
16
+ Driver-thread calls are strictly sequential: one facade call runs at a time; concurrent driving is out of scope.
17
+ One browser process per test: the session is owned by the test's runtime — no state is shared between tests through the library.
18
+ The start mode branches on the browser_endpoint setting of `Config`: empty — local launch with headless and the channel for chrome/msedge; set — connect over the Playwright ws endpoint: headless is ignored, channels do not apply, the browser setting selects the engine (see `playwright`).
19
+ All waits go through locators and expectations; fixed delays (time.sleep and similar) are forbidden.
20
+ The facade surface is a backward-compatibility contract: generated step code works only through `PageFacade` and LocatorFacade, so the existing method set must not break across library releases — extend, never rename or remove.
21
+
22
+ ---
23
+
24
+ "DriverSession(config: Config)":
25
+ location: session.py
26
+ annotations: |
27
+ Lifecycle owner of the Playwright sync driver and the browser process of one test.
28
+
29
+ `config`: project settings; the browser setting selects the browser of the {chromium, firefox, webkit, chrome, msedge} set — chrome and msedge launch the locally installed browser through the channel mechanism; headless controls the window visibility of a local launch; browser_endpoint switches the start to a remote connect (see `playwright`).
30
+
31
+ Requirements:
32
+ - The Playwright session lives in a dedicated driver thread owned by the session: every Playwright-touching operation of this type runs there, and the calling thread never holds a running asyncio loop after any call
33
+ methods:
34
+ "open_context() -> page: PageFacade": |
35
+ Open a fresh isolated context with one page of this test's browser.
36
+
37
+ Algorithm:
38
+ 1. Start lazily on the first call: constructing the session starts nothing — start the dedicated driver thread, then start Playwright inside it; an empty browser_endpoint — launch the selected engine locally with headless from the project settings and the channel for the chrome/msedge values; a set browser_endpoint — connect over the Playwright ws endpoint of the selected engine: headless is ignored and channels do not apply; a failed launch or connect stops the started driver and closes the thread, so a retry begins from a clean state
39
+ 2. Create a fresh isolated browser context and its page inside the driver thread (see `playwright`)
40
+ 3. Wrap the page into `PageFacade` bound to the driver thread and return it
41
+
42
+ Requirements:
43
+ - Each result is isolated from every other context
44
+ - A channel launch without the installed browser fails loudly with an actionable message naming the missing browser
45
+ - A failed connect fails loudly with an actionable message naming the endpoint
46
+ "close()": |
47
+ Stop the browser, the Playwright driver and the driver thread; safe to call when nothing was started.
48
+
49
+ "PageFacade(page: Page, context: BrowserContext)":
50
+ location: page.py
51
+ annotations: |
52
+ The narrow, stable facade of a single test page — the only page API the generated step code may use.
53
+ Wraps one isolated browser context created by `DriverSession`.
54
+
55
+ `page`: the wrapped Playwright page object; never exposed through the facade.
56
+ `context`: the isolated browser context owning the page; the boundary the close method closes.
57
+
58
+ Requirements:
59
+ - Every Playwright call runs in the driver thread of the owning session: the call blocks until it finishes, strictly one at a time, and the calling thread never adopts the Playwright event loop
60
+ - Locating methods never sleep: waiting is the locator's own auto-wait behavior
61
+ - Scroll methods never sleep: the scrolled state is awaited through locators and expectations
62
+ - aria_snapshot and screenshot reflect the state at call time
63
+
64
+ Constraints:
65
+ - No method exposes raw Playwright objects: the facade is the boundary generated code works against
66
+ properties:
67
+ "url -> str": |
68
+ The current page URL.
69
+ methods:
70
+ "open(url: str)": |
71
+ Navigate to `url` and wait for the load state (see `playwright`).
72
+ "find_by_role(role: str, name: str) -> element: LocatorFacade": |
73
+ Locate one element by its aria role and accessible name; returns a `LocatorFacade`.
74
+ "find_by_label(label: str) -> element: LocatorFacade": |
75
+ Locate one element by its associated label; returns a `LocatorFacade`.
76
+ "find_by_text(text: str) -> element: LocatorFacade": |
77
+ Locate one element by its visible text; returns a `LocatorFacade`.
78
+ "find_by_attribute(name: str, value: str) -> element: LocatorFacade": |
79
+ Locate one element by the value of the attribute `name` — the intended use is data-* attributes (data-test-id, data-qa and any other data attribute). Auto-waits exactly like the other locating methods.
80
+
81
+ `name`: the full attribute name, e.g. data-test-id.
82
+ `value`: the attribute value to match.
83
+ "find_by_css(selector: str) -> element: LocatorFacade": |
84
+ Locate one element by a CSS selector.
85
+
86
+ `selector`: a valid CSS selector expression, e.g. form > button.primary.
87
+ "find_by_xpath(xpath: str) -> element: LocatorFacade": |
88
+ Locate one element by an XPath expression.
89
+
90
+ `xpath`: a valid XPath expression, e.g. //button[@type='submit'].
91
+ "aria_snapshot() -> snapshot: str": |
92
+ The structured accessibility-tree representation of the page — the primary machine-readable page state (see `playwright`).
93
+ "screenshot() -> image: bytes": |
94
+ A full-page PNG image of the current state.
95
+ "scroll_to_element(element: LocatorFacade)": |
96
+ Scroll the page so `element` enters the viewport — inside its nearest scrollable ancestor when the element lives in a scrollable container (see `playwright`).
97
+
98
+ `element`: the located element to bring into view.
99
+ "scroll_down(pixels: int)": |
100
+ Scroll the page down by `pixels`.
101
+
102
+ `pixels`: a positive scroll amount in CSS pixels.
103
+ "scroll_up(pixels: int)": |
104
+ Scroll the page up by `pixels`.
105
+
106
+ `pixels`: a positive scroll amount in CSS pixels.
107
+ "scroll_to_bottom()": |
108
+ Scroll the page to its end.
109
+ "scroll_to_top()": |
110
+ Scroll the page to its start.
111
+ "scroll_into_view(element: LocatorFacade, container: LocatorFacade)": |
112
+ Bring `element` into the visible area of the specific scrollable `container` — for nested scrollables where the nearest-ancestor behavior of scroll_to_element is not enough (see `playwright`).
113
+
114
+ `element`: the located element to bring into view.
115
+ `container`: the located scrollable container, e.g. a carousel.
116
+ "scroll_container_down(container: LocatorFacade, pixels: int)": |
117
+ Scroll the scrollable `container` down by `pixels`.
118
+
119
+ `container`: the located scrollable container.
120
+ `pixels`: a positive scroll amount in CSS pixels.
121
+ "scroll_container_up(container: LocatorFacade, pixels: int)": |
122
+ Scroll the scrollable `container` up by `pixels`.
123
+
124
+ `container`: the located scrollable container.
125
+ `pixels`: a positive scroll amount in CSS pixels.
126
+ "close()": |
127
+ Close the isolated context of this page; the browser process keeps running.
128
+
129
+ "LocatorFacade(locator: Locator)":
130
+ location: page.py
131
+ annotations: |
132
+ An auto-waiting handle of one located element — the only element API the generated step code may use.
133
+
134
+ `locator`: the wrapped Playwright locator object; never exposed through the facade.
135
+
136
+ Requirements:
137
+ - Every action and expectation runs in the driver thread inherited from the page facade that created the handle
138
+ - Every action and expectation auto-waits for actionability (see `playwright`)
139
+ - Failed expectations raise assertion-style errors destined for failure classification
140
+
141
+ Constraints:
142
+ - No fixed delays; no raw Playwright objects exposed
143
+ methods:
144
+ "click()": |
145
+ Click the element, waiting for actionability.
146
+ "fill(value: str)": |
147
+ Set the text input value of the element to `value`.
148
+ "select_option(value: str)": |
149
+ Select the option with `value` in a list or combo box.
150
+ "expect_visible()": |
151
+ Assert the element is visible.
152
+ "expect_text(text: str)": |
153
+ Assert the element text equals or contains `text`.
154
+ "expect_enabled()": |
155
+ Assert the element is enabled.
156
+
157
+ ---
158
+
159
+ Author: Goga
160
+ CreatedAt: 07/09/26
161
+ Description: |
162
+ The Playwright sync driver of prettyplay: the per-test session with local launches and remote ws connects, and the narrow backward-compatible page facade with universal locators and scroll abilities for generated step code.
@@ -0,0 +1,6 @@
1
+ """Facade of the prettyplay.driver cell: the Playwright sync driver of prettyplay."""
2
+
3
+ from .page import LocatorFacade, PageFacade
4
+ from .session import DriverSession
5
+
6
+ __all__ = ["DriverSession", "LocatorFacade", "PageFacade"]