-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Playwright Backend Support to Galaxy Browser Automation Framework #21102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
96bda85 to
3027f19
Compare
|
The integration selenium test seems related ? |
| - 'packages/**' | ||
| schedule: | ||
| # Run at midnight UTC every Tuesday | ||
| - cron: '0 0 * * 2' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to do that ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This same line is in the selenium version of this file. I am happy to drop it though if you'd like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is both exciting and scary. I wouldn't want to figure out something like:
def upload_queue_local_file(self, test_path, tab_id="regular"):
if self.backend_type == "playwright":
with self.page.expect_file_chooser() as fc_info:
self.wait_for_and_click_selector(f"div#{tab_id} button#btn-local")
file_chooser = fc_info.value
file_chooser.set_files(test_path)
else:
self.wait_for_and_click_selector(f"div#{tab_id} button#btn-local")
file_upload = self.wait_for_selector(f'div#{tab_id} input[type="file"]')
file_upload.send_keys(test_path)
figuring this out for one automation frameworks tests my patience to its limit already ...
Is your assessment that we don't be needing to write test cases by hand anymore at all ?
I do like the added instruction files though, that's really cool.
Yeah - I imagine that at some point people will be writing Playwright only tests and the Selenium support in the framework layers and existing tests will be a passion project if anyone cares. I certainly don't want people to have to debug all tests in two frameworks - that would really suck. People should feel free to mark tests as selenium_only or playbook_only with decorators I think.
No - not at all. I've not gotten anything automated to work in a way I'm happy with - I think updating the selectors, maintaining the components, doing the work - is important and useful. Update: I mean these questions are disconnected but I do think the process of getting one test framework to run the test of the other at this point given the abstractions we have, examples already available, etc... would be pretty doable via Sonnet 4.5 - I have a markdown document I was going to throw at this and let Claude work through these but the PR is already very unwieldy. |
675753b to
a0921f2
Compare
Should allow pytest without loading the whole app.
Creates playwright.yaml workflow mirroring the existing selenium.yaml structure to run the test suite with the Playwright backend. The workflow maintains the same configuration as Selenium tests (parallel execution across 3 chunks, PostgreSQL service, client build dependencies) while setting GALAXY_TEST_DRIVER_BACKEND=playwright to use the Playwright backend. Key features: - Sets GALAXY_TEST_DRIVER_BACKEND=playwright for backend selection - Installs Playwright and Chromium browser with system dependencies in test job - Uses headless mode (GALAXY_TEST_SELENIUM_HEADLESS=1) for CI execution - Maintains same trigger paths, environment variables, and matrix strategy - Uploads test results and debug artifacts on failure Unlike Selenium which uses a separate setup_selenium.yaml reusable workflow for global chromedriver installation, Playwright installation happens directly in the test job since it requires the Python environment context (venv, working directory) to be available. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Replace custom BrowserAvailability class and global cache with two simple @lru_cache(maxsize=1) decorated functions. This provides the same caching behavior with cleaner, more Pythonic code. Benefits: - Simpler code: Reduced from ~60 lines to ~30 lines - Same functionality: Caches single result, avoids repeated browser launches - More Pythonic: Uses standard library caching instead of custom implementation - Cleaner API: No need to manage global cache object or expose the class Thanks to @mvdbeek for the suggestion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
The docstring was documenting a non-existent 'default_timeout' parameter and missing the actual 'timeout_handler' parameter. Updated to accurately document all parameters in the correct order. Changes: - Added missing 'timeout_handler' parameter documentation - Removed incorrect 'default_timeout' parameter - Described timeout_handler as callback function that returns timeout value Thanks to the PR review for catching this. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
a0921f2 to
3c1bd35
Compare
3884130 to
277c414
Compare
|
This PR was merged without a "kind/" label, please correct. |
Summary
This PR introduces comprehensive Playwright backend support to Galaxy's browser automation framework, enabling runtime selection between Selenium and Playwright while maintaining full backward compatibility. The work establishes a protocol-based architecture that abstracts ~60+ browser operations behind a unified interface (
HasDriverProtocolandWebElementProtocol), implements a complete Playwright driver matching the existing Selenium API, and introduces a proxy pattern enablingNavigatesGalaxyand all Galaxy tests to work seamlessly with both backends.The low-level browser interaction components now all have unit tests and the unit tests run against both implementations, the entire Galaxy Selenium test suite supports backend switching via
GALAXY_TEST_DRIVER_BACKENDenvironment variable, and GitHub Actions workflows validate both backends in CI.Establishing Abstractions in Existing Code
The first major challenge was identifying and eliminating direct Selenium API usage throughout the codebase. Through systematic analysis of
navigates_galaxy.py(the 2800+ line Galaxy-specific automation layer), we discovered 132+ instances of directself.driveraccess bypassing theHasDriverabstraction layer.Gap Analysis & Abstraction Work
We categorized these gaps and systematically addressed them:
JavaScript Execution (8 instances → Abstracted)
execute_script(script, *args)with Selenium'sargumentsarray patternset_local_storage(),remove_local_storage(),scroll_into_view(),set_element_value(),execute_script_click()Frame/Context Switching (4 instances → Abstracted)
switch_to_frame()to accept string (name/id), int (index), or WebElementswitch_to_default_content()for returning to main page contextNavigation & Cookies (Multiple instances → Abstracted)
navigate_to(url)andre_get_with_query_params()get_cookies()with consistent TypedDict return structurepage_source,current_url,page_titlepropertiesActionChains Patterns (5+ instances → Abstracted)
move_to_and_click(element)for hover-then-click patternhover(element)for mouse-over interactionsdrag_and_drop(source, target)using JavaScript events for cross-backend compatibilityWaitMethodsMixinwith 20+ wait methods shared between implementationsAlert Handling (Improved)
accept_alert()to return context managerwith driver.accept_alert(): driver.click_selector("#alert-button")Remaining Direct Element Access
While ~60 instances of element interaction remain (
.click(),.send_keys(),.get_attribute()on returned WebElements), these work correctly through theWebElementProtocolinterface that bothSelenium.WebElementand ourPlaywrightElementwrapper implement. This allows elements returned from find methods to be used identically regardless of backend.Playwright Implementation
Core Infrastructure (
has_playwright_driver.py)Created a complete
HasPlaywrightDriverclass (1134 lines) mirroring the entireHasDriverAPI:Element Finding & Selection
By.ID,By.CLASS_NAME,By.XPATH→ Playwright's#id,.class,xpath=..._selenium_locator_to_playwright_selector()for seamless conversionTargetcomponent systemWait Strategy Implementation
Implemented 6 wait condition methods using Playwright's native APIs:
_wait_on_condition_present()- usesstate="attached"_wait_on_condition_visible()- usesstate="visible"_wait_on_condition_clickable()- checksstate="visible"+is_enabled()_wait_on_condition_invisible()- usesstate="hidden"_wait_on_condition_absent()- usesstate="detached"_timeout_in_ms()) for Playwright's millisecond-based APIFrame Handling Architecture
_current_frameattributeAdvanced Features
argumentsarray to match Seleniumpage.evaluate()(no extra dependencies)page.keyboard.press(),element.hover(),element.click()Element Wrapper Pattern (
playwright_element.py)Created
PlaywrightElementclass wrapping Playwright'sElementHandleto implementWebElementProtocol:Key Features:
WebElementProtocolmethods (click,send_keys,clear,get_attribute, etc.)valueattribute usinginput_value()to match Selenium behaviorfind_element,find_elements)value_of_css_property()usingwindow.getComputedStyle()for CSS introspectionProtocol-Based Architecture & Runtime Dispatching
The Protocol Layer
Defined two core protocol interfaces enabling polymorphic backend usage:
HasDriverProtocol(has_driver_protocol.py- 495 lines)backend_typeproperty for runtime introspectionWebElementProtocol(web_element_protocol.py)text,click(),send_keys(),clear(),get_attribute(),is_displayed(),is_enabled(),value_of_css_property()WebElementPlaywrightElementwrapper for PlaywrightThe Proxy Pattern
HasDriverProxy(has_driver_proxy.py- 495 lines)_driver_implpropertyHasDriverProtocolNavigatesGalaxyRefactoringHasDriverto inheritingHasDriverProxyself.driveraccess (replaced with protocol methods)Runtime Backend Selection
ConfiguredDriver(driver_factory.py)backend_type: Literal["selenium", "playwright"]parameterbackend_type:Environment Variable Integration
GALAXY_TEST_DRIVER_BACKENDcontrols backend selection (defaults to "selenium")GALAXY_TEST_DRIVER_BACKEND=playwright pytest ...backend_type: playwrightin YAML configlib/galaxy_test/selenium/framework.py) passes toConfiguredDriverLifecycle Management
PlaywrightResourcesNamedTuple bundling(playwright, browser, page)quit()method: closes page, browser, stops playwrightSmart Components Integration
Updated
SmartComponentandSmartTargetto work with protocol-based drivers:HasDrivertoHasDriverProtocolTest Migration Progress
Unit Test Infrastructure
Comprehensive Test Coverage (
test_has_driver.py- 1246 lines)selenium,playwright,proxy-seleniumTestElementFinding(18 tests × 3 = 54 runs)TestVisibilityAndPresence(20 tests × 3 = 60 runs)TestWaitMethods(31 tests × 3 = 93 runs)TestClickAndInteraction,TestActionChainsAndKeys,TestFrameSwitchingTestJavaScriptExecution,TestCookieManagement,TestAccessibilityTestFormInteraction,TestInputValueAbstraction,TestSelectByValueTestCSSProperties,TestAlertHandling,TestScreenshotsTest Fixtures & Infrastructure
basic.html,accessibility.html,smart_components.html,frame.htmlTestHTTPServerserves pages locally during teststest_serverandbase_urlfixtures (fixed scope issues for monorepo execution)has_driver_instancefixture creates appropriate backendResults: ✅ All 150+ tests passing across all 3 backend configurations
Integration Test Migration (Galaxy Test Suite)
Migration Statistics:
@selenium_onlydecorator for Selenium-specific featuresMigration Tiers Completed:
Tier 1: MVP Targets (23 files, 31 tests) ✅
test_login.py,test_published_pages.py,test_anon_history.pyTier 2: Medium Complexity (26 files, 136 tests) ✅
test_registration.py,test_workflow_sharing.py,test_pages.py@selenium_onlyfor Select class usageTier 3: Complex (8 files, 101 tests) ✅
test_uploads.py,test_tool_form.py,test_workflow_editor.pyFramework Integration:
lib/galaxy_test/selenium/framework.pyto passbackend_typetoConfiguredDriver@selenium_only(reason)and@playwright_only(reason)decoratorsGALAXY_TEST_END_TO_END_CONFIGpage_source,get_screenshot_as_png(),execute_script())CI Workflows:
.github/workflows/playwright.yamlmirroring Selenium workflow structurerun_tests.shwith a-playwrightargument to mirror-seleniumand used this from the new CI workflow.Documentation
Comprehensive Guides Created:
README.rst: 480-line comprehensive package documentationHasDriverProtocol,WebElementProtocol)cli.py,DriverWrapper,dump_tour.pyexample)Automation Support:
/add-browser-operationslash command for automated operation scaffolding/add-browser-operation scroll element to center of viewportTechnical Achievements
Type Safety: ✅ All code passes
mypytype checking with strict modeTest Quality: ✅ 150+ unit tests, all parametrized for dual-backend validation
Backward Compatibility: ✅ Zero breaking changes
Performance: ✅ Tests run successfully in CI
Documentation: ✅ Extensive inline docs + external guides
has_playwright_driver.pyFuture Work
Playwright-Specific Features (Deferred)
The architecture supports adding Playwright-exclusive capabilities:
These can be added to a
HasPlaywrightExtensionsmixin when needed, with tests marked@playwright_only.Remaining selenium_only Tests (145+)
Tests marked
@selenium_onlycan be migrated by:tag_nameproperty)How to test the changes?
(Select all options that apply)
License