Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates WebDriver Atoms from Google Closure Library to TypeScript, implementing approximately 16 modules across storage, core atoms, inject wrappers, and exports. The migration includes new BUILD.bazel files, TypeScript configuration, and comprehensive implementation of storage APIs (localStorage, sessionStorage, AppCache), element manipulation, attribute handling, and input simulation.
Changes:
- Migration of WebDriver atoms from Closure to TypeScript with proper type safety
- New directory structure:
javascript/webdriver-atoms-ts/src/andjavascript/atoms-ts/src/ - BUILD.bazel configuration for ts_project targets with proper dependencies
- Storage modules (local_storage, session_storage, appcache) with complete implementations
- Core modules (element, attribute, inputs) for WebDriver operations
- Inject wrappers for script injection and element operations
Reviewed changes
Copilot reviewed 45 out of 45 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| py/BUILD.bazel | Updates Python build to use new TypeScript-compiled atoms |
| javascript/webdriver-atoms-ts/src/tsconfig.json | TypeScript configuration with strict mode enabled |
| javascript/webdriver-atoms-ts/src/storage/*.ts | Complete storage API implementations |
| javascript/webdriver-atoms-ts/src/inject/*.ts | Inject wrappers for script execution (CONTAINS STUBS) |
| javascript/webdriver-atoms-ts/src/element.ts | Element manipulation and property access |
| javascript/webdriver-atoms-ts/src/attribute.ts | Attribute getter/setter with proper type handling |
| javascript/webdriver-atoms-ts/src/inputs.ts | Input simulation wrappers |
| javascript/webdriver-atoms-ts/src/BUILD.bazel | Build configuration for webdriver-atoms-ts modules |
| javascript/atoms-ts/src/*.ts | Core atoms modules (error, bot, DOM, events, devices, etc.) |
| javascript/atoms-ts/src/BUILD.bazel | Build configuration for atoms-ts modules |
| WEBDRIVER_ATOMS_MIGRATION_PLAN.md | Detailed migration plan document |
| WEBDRIVER_ATOMS_DETAILED_ANALYSIS.md | Technical analysis document |
| function executeActionFunction(): string { | ||
| try { | ||
| const responseObj = { | ||
| status: 0, | ||
| value: null | ||
| }; | ||
| return JSON.stringify(responseObj); | ||
| } catch (err) { | ||
| const errorObj = { | ||
| status: 1, | ||
| value: { | ||
| message: (err as any).message || String(err) | ||
| } | ||
| }; | ||
| return JSON.stringify(errorObj); | ||
| } | ||
| } |
There was a problem hiding this comment.
All action functions (type, submit, clear, click, doubleClick, rightClick) call executeActionFunction() which returns stub implementations with null values. These functions need actual implementations that deserialize elements from the cache and execute the corresponding actions from the atoms-ts action module.
| function performSearch( | ||
| strategy: string, | ||
| using: string, | ||
| searchType: 'findElement' | 'findElements', | ||
| _optRoot?: JsonElement, | ||
| _optWindow?: executeScript.SerializedWindow | ||
| ): string { | ||
| try { | ||
| // In a real implementation, this would: | ||
| // 1. Deserialize optRoot from the inject cache if provided | ||
| // 2. Use the strategy and using to locate elements | ||
| // 3. Serialize found elements and cache them | ||
| // 4. Return their cache keys in the response | ||
|
|
||
| const responseObj = { | ||
| status: 0, | ||
| value: searchType === 'findElements' ? [] : null | ||
| }; | ||
| return JSON.stringify(responseObj); | ||
| } catch (err) { | ||
| const errorObj = { | ||
| status: 7, // NoSuchElement error code | ||
| value: { | ||
| message: `Unable to locate element with ${strategy}="${using}"` | ||
| } | ||
| }; | ||
| return JSON.stringify(errorObj); | ||
| } | ||
| } |
There was a problem hiding this comment.
The performSearch function contains only placeholder logic with comments indicating what a real implementation would do. It needs to actually deserialize the root element, use the strategy and locator to find elements, serialize found elements, cache them, and return their cache keys - not just return empty/null values.
| export function type( | ||
| element: Element, | ||
| keys: string[], | ||
| _keyboard?: Keyboard, | ||
| optPersistModifiers?: boolean | ||
| ): void { | ||
| const persistModifiers = !!optPersistModifiers; | ||
|
|
||
| interface KeySequence { | ||
| persist: boolean; | ||
| keys: (string | any)[]; | ||
| } | ||
|
|
||
| function createSequenceRecord(): KeySequence { | ||
| return { persist: persistModifiers, keys: [] }; | ||
| } | ||
|
|
||
| const convertedSequences: KeySequence[] = []; | ||
| let current = createSequenceRecord(); | ||
| convertedSequences.push(current); | ||
|
|
||
| const keyMap = getKeyMap(); | ||
|
|
||
| keys.forEach((sequence: string) => { | ||
| sequence.split('').forEach((char: string) => { | ||
| if (isWebDriverKey(char)) { | ||
| const webdriverKey = keyMap[char]; | ||
| if (webdriverKey === null) { | ||
| // Release modifier keys | ||
| convertedSequences.push((current = createSequenceRecord())); | ||
| if (persistModifiers) { | ||
| current.persist = false; | ||
| convertedSequences.push((current = createSequenceRecord())); | ||
| } | ||
| } else if (webdriverKey !== undefined) { | ||
| current.keys.push(webdriverKey); | ||
| } else { | ||
| throw Error( | ||
| `Unsupported WebDriver key: \\u${char.charCodeAt(0).toString(16)}` | ||
| ); | ||
| } | ||
| } else { | ||
| // Handle common character aliases | ||
| switch (char) { | ||
| case '\n': | ||
| current.keys.push('Enter'); | ||
| break; | ||
| case '\t': | ||
| current.keys.push('Tab'); | ||
| break; | ||
| case '\b': | ||
| current.keys.push('Backspace'); | ||
| break; | ||
| default: | ||
| current.keys.push(char); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // Execute sequences using action module | ||
| convertedSequences.forEach((sequence: KeySequence) => { | ||
| action.type(element, sequence.keys as string[]); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The type function has a parameter _keyboard?: Keyboard with an underscore prefix (indicating it's intentionally unused), but the function doesn't actually use the provided keyboard instance. Instead, it directly manipulates sequences and calls action.type. This breaks the API contract - if a keyboard instance is provided, it should be used for the typing operations.
| function executeDomFunction(): string { | ||
| try { | ||
| // In a real implementation, elements would be deserialized from the cache | ||
| const responseObj = { | ||
| status: 0, | ||
| value: null | ||
| }; | ||
| return JSON.stringify(responseObj); | ||
| } catch (err) { | ||
| const errorObj = { | ||
| status: 1, | ||
| value: { | ||
| message: (err as any).message || String(err) | ||
| } | ||
| }; | ||
| return JSON.stringify(errorObj); | ||
| } | ||
| } |
There was a problem hiding this comment.
Many inject functions return stub implementations with placeholder empty values. For example, findElement returns { status: 0, value: null } and getText calls executeDomFunction() which always returns null. These functions need actual implementations that perform their intended operations (finding elements, getting text, etc.) rather than returning placeholder responses.
| try { | ||
| // In a real implementation, this would: | ||
| // 1. Open the database using window.openDatabase(_databaseName, ...) | ||
| // 2. Create a transaction | ||
| // 3. Execute the query with the provided arguments (_args) | ||
| // 4. Handle results and errors | ||
| // 5. Call onDone with the serialized response | ||
|
|
||
| const responseObj = { | ||
| status: 0, | ||
| value: { | ||
| rowsAffected: 0, | ||
| insertId: null, | ||
| rows: [] | ||
| } | ||
| }; | ||
| onDone(JSON.stringify(responseObj)); |
There was a problem hiding this comment.
The executeSql function contains only a stub implementation with placeholder comments. Real SQL execution logic is missing - it should actually open the database, create a transaction, execute the query with provided arguments, and handle results/errors properly. The current implementation just returns empty results regardless of the query.
javascript/atoms-ts/src/dom.ts
Outdated
| import { getAttribute, getProperty, isElement as domCoreIsElement } from './domcore'; | ||
| import { standardizeColor } from './color'; | ||
| import * as userAgent from './userAgent'; | ||
| import * as bot from './bot'; |
There was a problem hiding this comment.
Unused import bot.
| import * as bot from './bot'; |
javascript/atoms-ts/src/dom.ts
Outdated
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | ||
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | ||
| ); |
There was a problem hiding this comment.
Unused variable CSS_TRANSFORM_MATRIX_REGEX.
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | |
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | |
| ); |
javascript/atoms-ts/src/events.ts
Outdated
|
|
||
| import { WebDriverError, ErrorCode } from './error'; | ||
| import * as userAgent from './userAgent'; | ||
| import * as bot from './bot'; |
There was a problem hiding this comment.
Unused import bot.
| import * as bot from './bot'; |
| import * as dom from './dom'; | ||
| import * as events from './events'; | ||
| import * as userAgent from './userAgent'; | ||
| import * as bot from './bot'; |
There was a problem hiding this comment.
Unused import bot.
| import * as bot from './bot'; |
javascript/atoms-ts/src/window.ts
Outdated
| * Atoms for simulating user actions against the browser window. | ||
| */ | ||
|
|
||
| import * as bot from './bot'; |
There was a problem hiding this comment.
Unused import bot.
| import * as bot from './bot'; |
4241b70 to
f9d5aa8
Compare
- Replace 3 JavaScript atoms with TypeScript versions: * getAttribute: //javascript/webdriver/atoms → //javascript/webdriver-atoms-ts/src:attribute * isDisplayed: //javascript/atoms/fragments → //javascript/webdriver-atoms-ts/src:dom * findElements: //javascript/atoms/fragments → //javascript/webdriver-atoms-ts/src:find_element - Add select_file rules to extract specific .js outputs from multi-output ts_project targets - Update visibility in webdriver-atoms-ts to public for cross-package access - Build verified: bazel build //py:selenium completes successfully This integrates the new type-safe TypeScript atoms into the Python WebDriver, replacing old JavaScript atom implementations with the new atoms-ts versions.
f9d5aa8 to
3f8b0ae
Compare
| @pytest.mark.xfail_wpewebkit()( | ||
| raises=AttributeError, reason="Logging API is no longer available" | ||
| ) |
There was a problem hiding this comment.
There's a syntax error in the decorator call on line 322. The decorator should be @pytest.mark.xfail_wpewebkit() (with empty parentheses) to match the pattern used on line 299-301, but instead it has an extra set of parentheses: @pytest.mark.xfail_wpewebkit()(). This will cause a runtime error when the test file is loaded.
| (globalThis as any).__isDisplayed__ = (element: any, optWindow?: any): string => { | ||
| return dom.isDisplayed(element, optWindow); |
There was a problem hiding this comment.
The return type annotation on line 4 is incorrect. The function returns a boolean value (dom.isDisplayed returns boolean), but the annotation specifies it returns string. This should be : boolean instead of : string.
| export function isDisplayed(_element: JsonElement, _optWindow?: executeScript.SerializedWindow): string { | ||
| return executeDomFunction(); | ||
| } |
There was a problem hiding this comment.
The return type annotation should match the return type of the imported function. The isDisplayed function at line 98 in inject/dom.ts returns string (stringified response), not string as a direct value. However, if dom.isDisplayed from line 2 returns a boolean, this wrapper should return boolean, not string. The type annotation is inconsistent with the actual return value.
|
There are open questions that need a village to help me answer.
|
- Restructured findElement/findElements to delegate to performSearch_ with search functions
- Implemented locateSingleElement and locateMultipleElements search function wrappers
- Separated response handling into wrapResponse and wrapError functions
- Changed locator format to object style: {strategy: using} to match Closure
- Added SearchFunction type for polymorphic search function signatures
- Renamed optional parameter _optWindow with underscore prefix to document intentional non-use
- All TypeScript compilation errors resolved (0 errors)
- Builds successfully with no breaking changes
- Maintains API compatibility with WebDriver protocol
…format - Added 'locators/*.ts' and 'locators/**/*.ts' to the find_element_bundle glob pattern in BUILD.bazel This ensures that locator strategy files are included when building the find_element_bundle, which was causing the 'Unexpected token ;' JavaScript error due to missing module imports - Fixed malformed JSDoc comment in exports/index.ts that was missing proper formatting Changed from 'Element locator strategies and finder functions. */' to proper multi-line format The find_element_bundle imports from ../locators/index.ts which was not being included in the esbuild process, causing esbuild to fail to resolve the imports and generate malformed JavaScript.
Some updates from Appium side.
Atoms that are used
So 22 atom files in Atoms that are not usedThese files exist in
So 23 atom files in |
|
We do not support IE 10. We technically support IE 11 via IE Mode in Edge, but I would be surprised if we ever did another IE Driver release. We could consider making that official with Selenium 5; we haven't touched IE Driver code in over 2 years, and I think Microsoft is in security-updates-only mode on it. Looks like Google first applies their own patch to our code (https://chromium.googlesource.com/chromium/src/+/refs/heads/main/third_party/selenium-atoms/patch.diff) before building what's in Did an AI review of chromedriver code compared to what we build in
These are the things that we are building but are not referenced:
|
|
One note from the Appium perspective: it would be ideal if the built atoms were simply made available as an NPM package we could consume. That would greatly simplify how we make them available as part of Appium. |
| if isDisplayed_js is None: | ||
| _load_js() | ||
| return self.parent.execute_script(f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self) | ||
| return self.parent.execute_script(f"{isDisplayed_js}; return __isDisplayed__.apply(null, arguments);", self) |
There was a problem hiding this comment.
__isDisplayed__ is referenced as a bare identifier, but the new bundle assigns it via globalThis.__isDisplayed__ = .... To avoid scope/strict-mode issues, consider calling it via globalThis.__isDisplayed__ (or window.__isDisplayed__) explicitly.
| export function many( | ||
| criteria: string | Record<string, unknown>, | ||
| _root: Document | Element | ||
| ): Element[] { | ||
| if (typeof criteria === 'string') { | ||
| try { | ||
| JSON.parse(criteria); | ||
| } catch (e) { | ||
| throw new Error(`Invalid relative locator criteria: ${criteria}`); | ||
| } | ||
| } | ||
|
|
||
| // This is a simplified implementation of relative locators | ||
| // A full implementation would need to handle all the relative locator filters | ||
| // such as: above, below, left of, right of, near | ||
|
|
||
| // For now, return an empty array as placeholder | ||
| // Full implementation would need to parse the filters and apply them | ||
| return []; | ||
| } |
There was a problem hiding this comment.
many() currently parses/validates the criteria but then always returns an empty array as a placeholder. Python uses findElements.js specifically to implement RelativeBy; returning [] here will make relative locators always find nothing. This strategy needs a real implementation or should throw a clear error so failures are obvious.
| // Wrapper for getAttribute that exports to window.__getAttribute__ | ||
| import { get } from './attribute'; | ||
|
|
||
| // Export function to window object for browser execution | ||
| (globalThis as any).__getAttribute__ = (element: Element, name: string): string | null => { | ||
| return get(element, name); | ||
| }; |
There was a problem hiding this comment.
New source files in this repo typically include the standard SFC Apache 2.0 license header. This wrapper file currently lacks it; please add the standard header for compliance/consistency.
| // Wrapper to make the attribute.get function callable from browser script execution | ||
| // This wraps the minified getAttribute function to work with Selenium's .apply() pattern | ||
| import { get } from './attribute'; | ||
|
|
||
| // Return the function directly - esbuild will bundle and return this | ||
| export default (element: Element, name: string): string | null => { | ||
| return get(element, name); | ||
| }; |
There was a problem hiding this comment.
New source files in this repo typically include the standard SFC Apache 2.0 license header. This wrapper file currently lacks it; please add the standard header for compliance/consistency.
| case 'id': | ||
| return root instanceof Document | ||
| ? root.getElementById(using) || null | ||
| : (root.querySelector(`#${CSS.escape(using)}`) || null); | ||
|
|
||
| case 'name': { | ||
| const nameElements = root instanceof Document | ||
| ? root.getElementsByName(using) | ||
| : (root as Element).querySelectorAll(`[name="${CSS.escape(using)}"]`); | ||
| return Array.from(nameElements); |
There was a problem hiding this comment.
This implementation relies on CSS.escape() for escaping IDs/names in selectors. CSS.escape is not available in all browsers Selenium supports, and it also isn't the right escaping for attribute selector string values. The existing atoms code uses its own escaping logic; consider reusing that approach here (or avoid querySelector for these cases) to preserve cross-browser behavior.
| // Wrapper for isDisplayed that exports to window.__isDisplayed__ | ||
| import * as dom from './inject/dom'; | ||
|
|
||
| (globalThis as any).__isDisplayed__ = (element: any, optWindow?: any): string => { | ||
| return dom.isDisplayed(element, optWindow); | ||
| }; | ||
|
|
||
|
|
There was a problem hiding this comment.
New source files in this repo typically include the standard SFC Apache 2.0 license header. This wrapper file currently lacks it; please add the standard header for compliance/consistency.
javascript/atoms-ts/src/dom.ts
Outdated
| /** | ||
| * A regular expression to match the CSS transform matrix syntax. | ||
| */ | ||
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | ||
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | ||
| ); | ||
|
|
There was a problem hiding this comment.
Unused variable CSS_TRANSFORM_MATRIX_REGEX.
| /** | |
| * A regular expression to match the CSS transform matrix syntax. | |
| */ | |
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | |
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | |
| ); |
| // @ts-ignore | ||
| import { Builder, WebDriver, Browser } from 'selenium-webdriver'; | ||
| // @ts-ignore | ||
| import { Environment } from 'selenium-webdriver/testing'; |
There was a problem hiding this comment.
Unused import Environment.
| import { Environment } from 'selenium-webdriver/testing'; |
| async function focusElement(elemId: string): Promise<void> { | ||
| await driver.executeScript(` | ||
| document.getElementById('${elemId}').focus(); | ||
| `); | ||
| } | ||
|
|
There was a problem hiding this comment.
Unused function focusElement.
| async function focusElement(elemId: string): Promise<void> { | |
| await driver.executeScript(` | |
| document.getElementById('${elemId}').focus(); | |
| `); | |
| } |
| return (root as Document).getElementById(id); | ||
| } | ||
| // Fallback to querySelector if getElementById is not available | ||
| return root.querySelector(`#${id.replace(/([\\!"#$%&'()*+,./:;?@[\\\]^`{|}~])/g, '\\$1')}`); |
There was a problem hiding this comment.
Character '\' is repeated in the same character class.
| @pytest.mark.xfail_wpewebkit()( | ||
| raises=AttributeError, reason="Logging API is no longer available" | ||
| ) |
There was a problem hiding this comment.
@pytest.mark.xfail_wpewebkit()( is invalid decorator syntax and will raise at import time (it calls the marker and then tries to call the result). It should be a single decorator call like the other xfail markers (pass raises/reason directly).
| export function type( | ||
| element: Element, | ||
| keys: string[], | ||
| _keyboard?: Keyboard, | ||
| optPersistModifiers?: boolean | ||
| ): void { | ||
| const persistModifiers = !!optPersistModifiers; | ||
|
|
||
| interface KeySequence { | ||
| persist: boolean; | ||
| keys: (string | any)[]; | ||
| } | ||
|
|
||
| function createSequenceRecord(): KeySequence { | ||
| return { persist: persistModifiers, keys: [] }; | ||
| } | ||
|
|
||
| const convertedSequences: KeySequence[] = []; | ||
| let current = createSequenceRecord(); | ||
| convertedSequences.push(current); | ||
|
|
||
| const keyMap = getKeyMap(); | ||
|
|
||
| keys.forEach((sequence: string) => { | ||
| sequence.split('').forEach((char: string) => { | ||
| if (isWebDriverKey(char)) { | ||
| const webdriverKey = keyMap[char]; | ||
| if (webdriverKey === null) { | ||
| // Release modifier keys | ||
| convertedSequences.push((current = createSequenceRecord())); | ||
| if (persistModifiers) { | ||
| current.persist = false; | ||
| convertedSequences.push((current = createSequenceRecord())); | ||
| } | ||
| } else if (webdriverKey !== undefined) { | ||
| current.keys.push(webdriverKey); | ||
| } else { | ||
| throw Error( | ||
| `Unsupported WebDriver key: \\u${char.charCodeAt(0).toString(16)}` | ||
| ); | ||
| } | ||
| } else { | ||
| // Handle common character aliases | ||
| switch (char) { | ||
| case '\n': | ||
| current.keys.push('Enter'); | ||
| break; | ||
| case '\t': | ||
| current.keys.push('Tab'); | ||
| break; | ||
| case '\b': | ||
| current.keys.push('Backspace'); | ||
| break; | ||
| default: | ||
| current.keys.push(char); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // Execute sequences using action module | ||
| convertedSequences.forEach((sequence: KeySequence) => { | ||
| action.type(element, sequence.keys as string[]); | ||
| }); |
There was a problem hiding this comment.
type() ignores the passed Keyboard instance (_keyboard is unused) and calls action.type(...) directly. This breaks the contract used by inputs.sendKeys, which expects webdriver.atoms.element.type to update the provided keyboard state so the caller can return the correct keyboard.getState() (e.g., for persisted modifiers). Consider routing key dispatch through the Keyboard instance (as the Closure implementation does) or remove the stateful API if it’s not supported.
| TEST_DEPS = [ | ||
| # requirement("debugpy"), | ||
| requirement("filetype"), | ||
| requirement("pytest"), |
There was a problem hiding this comment.
There’s a commented-out requirement("debugpy") entry inside TEST_DEPS. If this was only for local debugging, it should be removed to keep the BUILD file clean (and avoid buildifier churn). If it’s required, it should be added as an active dependency instead of commented.
javascript/atoms-ts/src/dom.ts
Outdated
| * A regular expression to match the CSS transform matrix syntax. | ||
| */ | ||
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | ||
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | ||
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | ||
| ); | ||
|
|
||
| /** |
There was a problem hiding this comment.
Unused variable CSS_TRANSFORM_MATRIX_REGEX.
| * A regular expression to match the CSS transform matrix syntax. | |
| */ | |
| const CSS_TRANSFORM_MATRIX_REGEX = new RegExp( | |
| 'matrix\\(([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+), ([\\d\\.\\-]+), ' + | |
| '([\\d\\.\\-]+)(?:px)?, ([\\d\\.\\-]+)(?:px)?\\)' | |
| ); | |
| /** |
| import { Environment } from 'selenium-webdriver/testing'; | ||
| // @ts-ignore |
There was a problem hiding this comment.
Unused import Environment.
| import { Environment } from 'selenium-webdriver/testing'; | |
| // @ts-ignore |
| function wrapError(error: any): string { | ||
| const message = error instanceof Error ? error.message : String(error); | ||
| return JSON.stringify({ | ||
| status: 7, // NoSuchElement error code | ||
| value: { | ||
| message: message | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
wrapError() hard-codes status 7 (NoSuchElement) for all failures, including invalid selector/XPath/CSS parse errors. The legacy atoms distinguish error types (e.g., invalid selector vs no such element), and Selenium clients rely on these codes to raise the correct exception types. Map thrown errors to the appropriate WebDriver error codes (at least invalid selector for CSS/XPath parse errors), or delegate error wrapping to the shared atoms-ts error/response utilities.
| TEST_DEPS = [ | ||
| # requirement("debugpy"), | ||
| requirement("filetype"), | ||
| requirement("pytest"), | ||
| requirement("pytest-instafail"), |
There was a problem hiding this comment.
Nit: the commented-out debugpy entry in TEST_DEPS isn’t indented like the rest of the list items. This tends to get reformatted by buildifier and creates noisy diffs; consider aligning it with the surrounding indentation or removing it if it’s not needed.
| export function mouseButtonDown(optState?: MouseState): MouseState { | ||
| const mouse = new Mouse(optState); | ||
| const target = mouse.getState().element; | ||
| if (target) { | ||
| action.click(target); | ||
| } | ||
| return mouse.getState(); | ||
| } | ||
|
|
||
| /** | ||
| * Releases the primary mouse button at the current location. | ||
| * | ||
| * @param optState Optional predefined mouse state. | ||
| * @returns The mouse state. | ||
| */ | ||
| export function mouseButtonUp(optState?: MouseState): MouseState { | ||
| const mouse = new Mouse(optState); | ||
| return mouse.getState(); | ||
| } |
There was a problem hiding this comment.
mouseButtonDown/mouseButtonUp don’t match the established atom semantics: mouseButtonDown currently calls action.click() (which performs a full click sequence), and mouseButtonUp is a no-op. The legacy implementation presses/releases the mouse button (mousedown/mouseup) and updates mouse state accordingly. This will break advanced user interactions that rely on separate down/up events (e.g., click-and-hold, drag sequences).
| export function many( | ||
| criteria: string | Record<string, unknown>, | ||
| _root: Document | Element | ||
| ): Element[] { | ||
| if (typeof criteria === 'string') { | ||
| try { | ||
| JSON.parse(criteria); | ||
| } catch (e) { | ||
| throw new Error(`Invalid relative locator criteria: ${criteria}`); | ||
| } | ||
| } | ||
|
|
||
| // Relative locators require a full implementation to parse criteria and apply filters | ||
| // This functionality is not yet available in this build | ||
| throw new Error( | ||
| 'Relative locator strategies are not implemented in this build. ' + | ||
| 'This feature requires implementation of filters: above, below, left of, right of, near' | ||
| ); | ||
| } |
There was a problem hiding this comment.
relativeLocator.many() is invoked for RelativeBy locators, but the current many() implementation always throws an Error (“not implemented in this build”). This makes Python’s (and any consumer’s) relative locators unusable once findElements.js is switched to this TS bundle. Implement the relative locator strategy (parse {root, filters} and apply filters) or keep using the existing Closure-based find-elements atom until parity is reached.
| // Wrapper for isDisplayed that exports to window.__isDisplayed__ | ||
| import * as dom from './inject/dom'; | ||
|
|
||
| (globalThis as any).__isDisplayed__ = (element: any, optWindow?: any): boolean => { | ||
| return dom.isDisplayed(element, optWindow); | ||
| }; |
There was a problem hiding this comment.
This wrapper exports __isDisplayed__ but delegates to ./inject/dom.isDisplayed(), which (in this PR) returns a stringified response object, not a boolean. As a result, WebElement.is_displayed() will start returning a string (truthy) instead of a real boolean. The wrapper should call the actual DOM visibility implementation (equivalent to legacy bot.dom.isShown) and return a boolean.
| if isinstance(by, RelativeBy): | ||
| _pkg = ".".join(__name__.split(".")[:-1]) | ||
| raw_data = pkgutil.get_data(_pkg, "findElements.js") | ||
| if raw_data is None: | ||
| raise FileNotFoundError(f"Could not find findElements.js in package {_pkg}") | ||
| raise FileNotFoundError( | ||
| f"Could not find findElements.js in package {_pkg}" | ||
| ) | ||
| raw_function = raw_data.decode("utf8") | ||
| find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);" | ||
| return self.execute_script(find_element_js, by.to_dict()) | ||
| find_element_js = ( | ||
| f"{raw_function}; return __findElements__.apply(null, arguments);" | ||
| ) | ||
| return self.execute_script(find_element_js, by.to_dict()) or [] |
There was a problem hiding this comment.
The RelativeBy branch now loads findElements.js from the new TS bundle and invokes __findElements__, but the bundle’s relative locator implementation currently throws “not implemented” (see webdriver-atoms-ts/src/locators/strategies/relative.ts). This will cause driver.find_elements(RelativeBy...) to fail at runtime. Keep using the existing Closure find-elements atom for RelativeBy until the TS relative locator strategy is fully implemented and returns real DOM elements.
| return self.parent.execute_script( | ||
| f"{isDisplayed_js}; return globalThis.__isDisplayed__.apply(null, arguments);", | ||
| self, | ||
| ) |
There was a problem hiding this comment.
The new is_displayed script now expects the bundled atom to expose globalThis.__isDisplayed__ and return a boolean. With the current TS bundle (is-displayed-wrapper.ts -> inject/dom.ts), the function returns a JSON string (and always null), so WebElement.is_displayed() will return the wrong type/value. The bundled atom needs to return a real boolean (legacy behavior: bot.dom.isShown).
🔗 Related Issues
💥 What does this PR do?
It moves things from Closure to Typescript
🔧 Implementation Notes
💡 Additional Considerations
🔄 Types of changes
Cleanup (formatting, renaming)
Bug fix (backwards compatible)
New feature (non-breaking change which adds functionality and tests!)
Breaking change (fix or feature that would cause existing functionality to change)
All of the above?