Skip to content

Conversation

@tristanperalta
Copy link

Summary

This PR brings playwright-elixir significantly closer to feature parity with the official TypeScript Playwright client.

New Modules

Module Description
Playwright.Artifact Handle trace/video artifacts
Playwright.Clock Time manipulation for testing (freeze, fast-forward)
Playwright.Coverage JS/CSS code coverage collection
Playwright.LocatorHandlers Auto-dismiss dialogs/overlays
Playwright.WebSocketRoute WebSocket mocking and interception

New Methods by Module

Page

  • add_script_tag/2, add_style_tag/2 - Inject scripts/styles
  • add_locator_handler/4, remove_locator_handler/2 - Auto-dismiss overlays
  • bring_to_front/1, viewport_size/1 - Window management
  • check/3, uncheck/3, set_checked/4, set_input_files/4 - Form interactions
  • console_messages/1, page_errors/1 - Debug info collection
  • emulate_media/2 - CSS media emulation
  • eval_on_selector_all/4 - Evaluate JS on all matches
  • frame/2 - Find frames by name/URL
  • go_back/2, go_forward/2, wait_for_url/3 - Navigation
  • opener/1 - Get popup opener
  • pause/1 - Open Playwright Inspector
  • pdf/2 - Generate PDFs (Chromium)
  • query_selector/3, query_selector_all/3 - DOM queries
  • request_gc/1 - Trigger garbage collection
  • route_web_socket/3 - WebSocket interception
  • tap/3, type/4 - Touch and keyboard input
  • video/1 - Access video recordings
  • wait_for_navigation/3 - Wait for navigation
  • wait_for_request/4, wait_for_response/4 - Network waiting

BrowserContext

  • route_web_socket/3 - WebSocket interception
  • set_geolocation/2 - Mock geolocation
  • set_http_credentials/2 - HTTP auth
  • storage_state/1-2 - Session persistence
  • unroute_all/2 - Clear all routes

Browser

  • browser_type/1 - Get browser type
  • is_connected/1 - Check connection status
  • new_browser_cdp_session/1 - CDP access (Chromium)
  • start_tracing/2, stop_tracing/1 - Performance tracing

Locator

  • and_/2, or_/2 - Combine locators
  • aria_snapshot/2 - Accessibility tree snapshot
  • content_frame/1 - Navigate into iframes
  • filter/2 - Filter by conditions
  • frame_locator/2 - Nested frame navigation
  • get_by_role/3, get_by_test_id/2, get_by_label/3 - Semantic locators
  • get_by_placeholder/3, get_by_alt_text/3, get_by_title/3
  • highlight/1 - Visual debugging
  • page/1 - Get containing page
  • press_sequentially/3 - Type character by character

Frame

  • child_frames/1, parent_frame/1, is_detached/1 - Hierarchy
  • frame_element/1 - Get iframe element
  • page/1 - Get containing page
  • wait_for_navigation/3 - Wait for navigation

Other Modules

  • Dialog - Full dialog handling (accept/dismiss/message)
  • Download - Save downloads, get paths
  • FileChooser - Handle file inputs
  • FrameLocator - Navigate iframes
  • Mouse - Click, move, wheel, drag
  • Route - abort/2 for request blocking
  • Touchscreen - tap/3 for touch events
  • Tracing - Record/export traces
  • Video - Save video recordings

Infrastructure Improvements

  • Added mix precommit task (format, credo, dialyzer, test)
  • Added feature parity tracking document
  • Fixed dialyzer errors and credo warnings
  • Upgraded dependencies (cowlib, gun, etc.)
  • Improved error handling in Browser.new_page/2

- Upgrade Elixir from 1.16.2-otp-26 to 1.18.4-otp-27
- Upgrade Erlang from 26.2.5 to 27.3.4.6
- Upgrade Node.js from 22.2.0 to 22.21.1
- Migrate from asdf (.tool-versions) to mise (.mise.toml)
- Update CI workflow to use new versions
- Fix `mix playwright.install` to support Arch Linux and Ubuntu
- Add OS detection to install system dependencies appropriately
- Install both Chromium and Firefox browsers explicitly

The playwright install task now detects the OS and:
- On Arch Linux: Installs dependencies via pacman, then browsers
- On Ubuntu/Debian: Uses --with-deps flag as before
- On unknown OS: Installs browsers without system dependencies
- credo: 1.6 → 1.7
- dialyxir: 1.1 → 1.4
- esbuild: 0.8.1 → 0.10
- ex_doc: 0.34 → 0.39
- mix_audit: 1.0 → 2.1
- recase: 0.7 → 0.9

Note: cowlib and gun cannot be upgraded due to playwright_assets
constraints requiring cowlib ~> 2.7.3
- Use forked playwright_assets (github: tristanperalta/playwright-assets)
  to unblock dependency upgrades
- Upgrade cowlib 2.7.3 → 2.16.0, gun 1.3.3 → 2.2.0, cowboy 2.6.3 → 2.14.2
- Fix gun 2.x API: ws_send/2 → ws_send/3 (requires stream_ref)
- Fix regex route patterns: use camelCase keys (regexSource/regexFlags)
  and convert Elixir regex opts to JS flags
- Rename unit_test.ex → unit_case.ex to silence ExUnit warning
Credo fixes:
- route_handler.ex: Use Enum.map_join instead of Enum.map |> Enum.join
- config.ex: Remove parentheses from zero-arity function definitions
- page.ex: Alphabetize alias groups
- channel.ex: Extract async_evaluate/2 to reduce nesting depth
- frame.ex: Split select_option_values into pattern-matched clauses
- .credo.exs: Disable PredicateFunctionNames (is_* matches Playwright API)

Documentation:
- Add man/guides/contributing.md documenting known issues and improvements
- Add contributing guide to mix.exs docs config
Dialyzer fixes:
- Fix @SPEC for BrowserContext.route/4, unroute/3, Page.route/4
- Return type changed from :ok to t() | {:error, Channel.Error.t()}
- Added Regex.t() to pattern parameter types

New precommit task (mix precommit):
- compile --warnings-as-errors
- format --check-formatted
- credo --strict
- dialyzer
- test

Also:
- Fix trailing blank lines (mix format)
Browser.new_page/2 now returns {:ok, Page.t()} instead of Page.t()

Changes:
- Browser.new_page/2 now returns {:ok, page} tuple with proper error handling
- Page.on/3 validates events against whitelist to prevent atom exhaustion
- ChannelOwner.module/1 uses reraise instead of exit() for proper error handling
- Page.close/1 uses latest channel state for owned_context check
- Page.request/1 fetches fresh page state for owned_context access
- Remove dead code: FetchRequest module (not in modern Playwright API)
- Fix with_latest/2 to explicitly discard first find result

Test updates:
- Update all tests to use {:ok, page} pattern matching
- Use Page.owned_context(page) instead of page.owned_context field access
- Set async: false for browser/context tests needing exclusive access
- Add man/guides/feature_parity.md documenting implementation status
  of all Playwright features compared to TypeScript client
- Add .claude/ to .gitignore
- Add feature_parity.md to mix.exs docs config
Implement Page and Frame navigation methods:
- Page.go_back/2 - Navigate to previous page in history
- Page.go_forward/2 - Navigate to next page in history
- Page.wait_for_url/3 - Wait for URL to match pattern
- Frame.wait_for_url/3 - Same for frames

wait_for_url supports:
- Glob patterns (e.g., "**/login")
- Regex patterns (e.g., ~r/dashboard$/)
- Function predicates (e.g., fn url -> ... end)

Implementation uses polling approach since wait_for_navigation
is not yet implemented.
Add support for handling browser dialogs (alert, confirm, prompt, beforeunload):
- Dialog.accept/1 and accept/2 for accepting dialogs with optional prompt text
- Dialog.dismiss/1 for dismissing/canceling dialogs
- Dialog.message/1, type/1, default_value/1, page/1 property accessors

Note: Dialog handlers should spawn a Task to avoid deadlock with Connection GenServer
when calling Dialog.accept/dismiss from within event handlers.
Implement modern locator methods for Page, Frame, and Locator modules:
- get_by_role/3 with full options support (name, checked, disabled, etc.)
- get_by_test_id/2 for data-testid attribute selection
- get_by_label/3 for label-based element selection

All methods follow the existing three-layer delegation pattern and support
chaining.
- Replace references to undefined set_default_timeout/2 functions with generic text
- Replace references to hidden Channel.Error module with generic error types
- Use term() in @SPEC for error returns to avoid hidden module references
Replace references to hidden/private types and functions with generic text:
- Channel.Error.t() → term()
- Playwright.SDK.Channel.Event → generic "event struct" text
- Playwright.SDK.Config types → map() with descriptive text
- Playwright.APIResponse.t() → struct()
- Playwright.CDPSession.t() → struct()
- set_default_timeout/2 references → "BrowserContext or Page timeout settings"
- JSHandle.dispose/1 references → generic "disposed" text
- BrowserContext.new_page/2 → new_page/1 (correct arity)
- Fix unclosed backquote in Locator.all/1 docs
Implement Page.wait_for_navigation and Frame.wait_for_navigation to wait
for navigation events with optional URL filtering and load state options.

Features:
- Trigger function support to avoid race conditions
- URL pattern matching (glob, regex, or function predicate)
- Configurable wait_until load state
- Timeout option with 30s default

Also fixes a bug in wait_for_load_state where the predicate checked the
event's load state directly instead of fetching the updated frame state,
causing "load" state waits to timeout when other events arrived first.
Implement filter/2 to narrow down locator matches by:
- has_text/has_not_text: Filter by text content (string or regex)
- has/has_not: Filter by presence of nested locator
- visible: Filter by element visibility

Filters can be combined and chained for complex selections.
Implement storage_state to capture cookies and localStorage for saving
and restoring browser sessions. Supports optional path option to save
state as JSON file.

Also adds Response.parse/2 clauses to handle the storage state response
format which contains both cookies and origins keys.
Enables location-based testing by overriding the browser's geolocation.
Accepts a map with latitude, longitude, and optional accuracy parameters.
Implements full Download module with Artifact backing:
- Download.from_event/1 to create Download from event
- save_as/2, path/1, failure/1, cancel/1, delete/1
- url/1, suggested_filename/1, page/1 accessors
Implements click, dblclick, move, down, up, and wheel methods following
the Keyboard module pattern.
Implements FrameLocator with all locator methods: first, last, nth,
frame_locator, locator, owner, and all get_by_* methods. Adds
Page.frame_locator/2 and Frame.frame_locator/2 entry points.
Implements PDF generation with options for format, margins, scale,
headers/footers, and more. Chromium headless only.

Also adds response parsing for :pdf channel responses.
Implement full Tracing API for debugging and analysis:
- start/2, start_chunk/2 for beginning trace recording
- stop/2, stop_chunk/2 for stopping and saving traces
- group/3, group_end/1 for organizing actions in trace viewer

Access via BrowserContext.tracing/1 property accessor.
Implements three missing getBy* methods across Page, Frame, and Locator
modules for consistency with FrameLocator (which already had them):

- get_by_placeholder/3: locate inputs by placeholder attribute
- get_by_alt_text/3: locate elements by alt attribute
- get_by_title/3: locate elements by title attribute

All methods support the :exact option for case-sensitive whole-string
matching (default: false for partial match).
Add delegation methods for element state and content queries:
- inner_text/3, inner_html/3, input_value/3
- is_checked/3, is_disabled/3, is_editable/3
- is_enabled/3, is_hidden/3, is_visible/3
- bring_to_front/1: Brings page to front (activates the tab)
- viewport_size/1: Returns current viewport dimensions as %{width, height}

Also added @Property :viewport_size to sync state, and updated
set_viewport_size/2 to patch local state for immediate reads.
Implement FileChooser as a simple struct (like Download) with:
- from_event/1 to create FileChooser from :file_chooser events
- element/1, is_multiple/1, page/1 accessors
- set_files/3 delegating to ElementHandle.set_input_files/3

Key implementation details:
- Fixed Page.on/3 to bind :file_chooser to page channel (not context)
- Added ElementHandle.set_input_files/3 with file payload preparation
- Uses Playwright protocol format (payloads key, timeout required)
…ached

Implement frame tree navigation methods to traverse the frame hierarchy:
- page/1: Returns the Page containing the frame
- parent_frame/1: Returns the parent frame (nil for main frame)
- child_frames/1: Returns list of child frames
- is_detached/1: Returns whether frame is detached from DOM
- name/1: Returns the frame name
Selector-based methods that delegate to Frame.
Add Frame.highlight/2 to highlight elements matching a selector, useful
for debugging. Locator.highlight/1 delegates to Frame.highlight/2.

Locator.page/1 returns the Page containing the locator by delegating to
the existing Frame.page/1 implementation.
Implement methods to wait for network requests/responses matching URL
patterns or custom predicates:
- wait_for_request/4: Wait for matching request (glob, regex, or function)
- wait_for_response/4: Wait for matching response (glob, regex, or function)

Both methods support:
- URL glob patterns (e.g., "**/api/users")
- Regex patterns
- Custom predicate functions for flexible matching
- Trigger functions to avoid race conditions
- Configurable timeout

Also fixes flaky frame hierarchy test by using more specific URL matching.
is_connected/1 checks if the browser is still in the channel catalog,
returning false after the browser has been closed.

browser_type/1 returns the BrowserType that launched the browser by
accessing the parent field from ChannelOwner.
Implement Page.frame/2 method to locate frames matching specified criteria:
- By name string (shorthand): Page.frame(page, "frame-name")
- By name map: Page.frame(page, %{name: "frame-name"})
- By URL with glob/regex/predicate: Page.frame(page, %{url: pattern})
Allows emulating CSS media features including media type (screen/print),
color scheme (dark/light), reduced motion, forced colors, and contrast
preferences. Pass nil to reset any option to the default value.
Implement methods:
- Page.opener/1: Returns the page that opened this popup, or nil
- Frame.frame_element/1: Returns the iframe ElementHandle for a frame
- Touchscreen.tap/3: Dispatches touch events at given coordinates
Implement Clock module for controlling time:
- install/2: Initialize fake timers with optional start time
- fast_forward/2: Jump forward, firing timers
- pause_at/2: Pause clock at specific time
- resume/1: Resume paused clock
- run_for/2: Advance by duration, firing timers
- set_fixed_time/2: Freeze time (doesn't advance)
- set_system_time/2: Set time but allow natural advance

Supports time as epoch ms, ISO strings, or DateTime structs.
Supports duration as ms or "hh:mm:ss" format.
Adds methods to inject JavaScript and CSS into pages:
- Frame.add_script_tag/2 and Frame.add_style_tag/2 send channel commands
- Page methods delegate to Frame via main_frame()
- Both return ElementHandle for the added element
…ments

Delegates to Frame.eval_on_selector_all which was already implemented.
Page and BrowserContext now support:
- set_default_timeout/2 - Set default timeout for operations
- set_default_navigation_timeout/2 - Set default navigation timeout
- set_extra_http_headers/2 - Set extra HTTP headers for requests
- unroute_all/2 - Remove all route handlers (Page only)
Implements browser-level tracing for performance profiling:
- start_tracing/3: Begin recording with optional screenshots/categories
- stop_tracing/1: Stop and return trace data as binary

Trace output is JSON format viewable in Chrome DevTools or trace.playwright.dev.
- Route.abort/2: Aborts a route with optional error code
- Frame.aria_snapshot/3: Gets ARIA accessibility snapshot for selector
- Locator.aria_snapshot/2: Delegates to Frame.aria_snapshot
- Added response parser for snapshot results
- Implement Video module with path/1, save_as/2, delete/1 methods
- Add Page.video/1 to access video recordings
- Use ETS-based storage to persist videos across page lifecycle
- Fix Artifact.delete/1 and cancel/1 to return :ok consistently
- Video events require actual rendered content to trigger frame capture
Sets HTTP authentication credentials for all requests in the context.
Pass nil to disable authentication.

Also adds TODO note for Locator.describe/2 which requires Playwright > 1.49.1
(internal:describe selector engine not available in current version).
- Browser.new_browser_cdp_session/1: Creates CDP session at browser level (Chromium only)
- BrowserContext.background_pages/1: Returns background pages (stub)
- BrowserContext.service_workers/1: Returns service workers (stub)

Also fixes feature_parity.md: removed incorrect Locator.owner entry
(only exists on FrameLocator which is already implemented).
Implements locator handlers for auto-dismissing overlays, dialogs, and
cookie banners during page interactions. Handlers execute when the
specified element becomes visible and block actions until resolved.
Implements WebSocket routing for intercepting, mocking, and modifying
WebSocket connections. Includes WebSocketRoute module with support for:
- Sending/receiving messages to/from page
- Connecting to actual server (proxy mode)
- Closing connections with custom codes/reasons
- Message and close event handlers
Implement Coverage methods for Chromium:
- start_js_coverage/2 and stop_js_coverage/1
- start_css_coverage/2 and stop_css_coverage/1

Uses string command names to preserve JS/CSS acronym casing.
These methods return collected console messages and page errors
(up to 200 items). Requires Playwright > 1.49.1 to function.

Tests are skipped until Playwright is upgraded.
- pause/1: Opens the Playwright Inspector for interactive debugging
- request_gc/1: Triggers JavaScript garbage collection in the browser
- tap/3: Performs touch tap action (delegates to Frame.tap)
- type/4: Types text character by character (deprecated, use fill)
Add @doc blocks to Frame.drag_and_drop/4, Page.drag_and_drop/4, and
Locator.drag_to/3 explaining available options like :force, :steps,
:trial, :source_position, and :target_position.

These options help with JS drag libraries (like SortableJS) that rely
on mousemove events rather than HTML5 drag-and-drop.
{:jason, "~> 1.4"},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:playwright_assets, "1.49.1", only: [:test]},
{:playwright_assets, github: "tristanperalta/playwright-assets", only: [:test]},
Copy link
Author

@tristanperalta tristanperalta Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to take note that I forked playwright_assets so that I can upgrade gun.

@CryptoNawwa
Copy link

Hey @tristanperalta any way I can contact / speak to you ? 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants