diff --git a/scripts/test-bidi-devtools.js b/scripts/test-bidi-devtools.js index 18edccd..d447825 100755 --- a/scripts/test-bidi-devtools.js +++ b/scripts/test-bidi-devtools.js @@ -89,7 +89,13 @@ drop.addEventListener('dragover', (e)=>e.preventDefault()); ` ); - await firefox.dragAndDropBySelectors('#drag', '#drop'); + const dndSnapshot = await firefox.takeSnapshot(); + const dragNode = dndSnapshot.json.root.children?.find((n) => n.id === 'drag'); + const dropNode = dndSnapshot.json.root.children?.find((n) => n.id === 'drop'); + if (!dragNode?.uid || !dropNode?.uid) { + throw new Error('drag & drop: elements not found in snapshot'); + } + await firefox.dragByUidToUid(dragNode.uid, dropNode.uid); const ok = await firefox.evaluate("return !!document.querySelector('#ok')"); console.log(ok ? '✅ Drag & drop worked\n' : '❌ Drag & drop failed\n'); } catch (e) { @@ -108,13 +114,19 @@ drop.addEventListener('dragover', (e)=>e.preventDefault()); await loadHTML( firefox, - `Upload Test + `Upload Test ` ); - await firefox.uploadFileBySelector('#file', filePath); + // The input has to remain visible: snapshots skip elements hidden via CSS. + const uploadSnapshot = await firefox.takeSnapshot(); + const fileNode = uploadSnapshot.json.root.children?.find((n) => n.id === 'file'); + if (!fileNode?.uid) { + throw new Error('file upload: input not found in snapshot'); + } + await firefox.uploadFileByUid(fileNode.uid, filePath); const ok = await firefox.evaluate("return document.body.getAttribute('data-ok') === '1'"); console.log(ok ? '✅ File upload worked\n' : '❌ File upload failed\n'); } catch (e) { diff --git a/src/firefox/dom.ts b/src/firefox/dom.ts index a03f696..8702941 100644 --- a/src/firefox/dom.ts +++ b/src/firefox/dom.ts @@ -2,7 +2,7 @@ * DOM interactions: evaluate, element lookup, input actions */ -import { By, Key, WebDriver, WebElement } from 'selenium-webdriver'; +import { Key, WebDriver, WebElement } from 'selenium-webdriver'; import type { Driver as FirefoxDriver } from 'selenium-webdriver/firefox.js'; import type { Actions } from 'selenium-webdriver/lib/input.js'; @@ -154,27 +154,6 @@ export class DomInteractions { private resolveUid?: (uid: string) => Promise ) {} - // ============================================================================ - // Element polling helpers - // ============================================================================ - - /** - * Poll for an element matching a CSS selector until found or timeout. - */ - private async waitForElement(selector: string, timeout = 5000): Promise { - const deadline = Date.now() + timeout; - let lastError: Error | undefined; - while (Date.now() < deadline) { - try { - return await this.driver.findElement(By.css(selector)); - } catch (e) { - lastError = e instanceof Error ? e : new Error(String(e)); - } - await new Promise((r) => setTimeout(r, 100)); - } - throw lastError ?? new Error(`Element not found: ${selector}`); - } - /** * Wait until an element reports isDisplayed(), ignoring failures. */ @@ -193,94 +172,6 @@ export class DomInteractions { // Visibility wait is best-effort; don't throw } - // ============================================================================ - // Selector-based input methods - // ============================================================================ - - /** - * Click element by CSS selector - */ - async clickBySelector(selector: string): Promise { - const el = await this.waitForElement(selector, 5000); - await this.waitForVisible(el, 5000); - await el.click(); - } - - /** - * Hover over element by CSS selector - */ - async hoverBySelector(selector: string): Promise { - const el = await this.waitForElement(selector, 5000); - await this.driver.actions({ async: true }).move({ origin: el }).perform(); - } - - /** - * Fill input field by CSS selector - */ - async fillBySelector(selector: string, text: string): Promise { - const el = await this.waitForElement(selector, 5000); - try { - await el.clear(); - } catch { - // Some inputs may not support clear(); fall back to select-all + delete - await el.sendKeys(Key.chord(Key.CONTROL, 'a'), Key.DELETE); - } - await el.sendKeys(text); - } - - /** - * Drag & drop using JS events fallback (DataTransfer). - * Works on simple pages; not guaranteed for all custom DnD libs. - */ - async dragAndDropBySelectors(sourceSelector: string, targetSelector: string): Promise { - await this.driver.executeScript( - ` - var srcSel = arguments[0], tgtSel = arguments[1]; - var src = document.querySelector(srcSel); - var tgt = document.querySelector(tgtSel); - if (!src || !tgt) throw new Error('dragAndDrop: element not found'); - function dispatch(type, target, dt) { - var evt = new DragEvent(type, { bubbles: true, cancelable: true, dataTransfer: dt }); - return target.dispatchEvent(evt); - } - var dt = typeof DataTransfer !== 'undefined' ? new DataTransfer() : undefined; - dispatch('dragstart', src, dt); - dispatch('dragenter', tgt, dt); - dispatch('dragover', tgt, dt); - dispatch('drop', tgt, dt); - dispatch('dragend', src, dt); - `, - sourceSelector, - targetSelector - ); - } - - /** - * File upload: unhide if needed, then send local path to . - */ - async uploadFileBySelector(selector: string, filePath: string): Promise { - const el = await this.waitForElement(selector, 5000); - // Ensure it's an ; if hidden, unhide via JS - await this.driver.executeScript( - ` - var sel = arguments[0]; - var e = document.querySelector(sel); - if (!e) throw new Error('uploadFile: element not found'); - if (e.tagName !== 'INPUT' || e.type !== 'file') - throw new Error('uploadFile: selector must target '); - var style = window.getComputedStyle(e); - if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { - var s = e.style; - s.display = 'block'; s.visibility = 'visible'; s.opacity = '1'; - s.position = 'fixed'; s.left = '0px'; s.top = '0px'; - s.zIndex = '2147483647'; - } - `, - selector - ); - await el.sendKeys(filePath); - } - // ============================================================================ // UID-based input methods // ============================================================================ diff --git a/src/firefox/index.ts b/src/firefox/index.ts index 161e54d..1ca6227 100644 --- a/src/firefox/index.ts +++ b/src/firefox/index.ts @@ -139,41 +139,6 @@ export class FirefoxClient { ); } - async clickBySelector(selector: string): Promise { - if (!this.dom) { - throw new Error('Not connected'); - } - return await this.dom.clickBySelector(selector); - } - - async hoverBySelector(selector: string): Promise { - if (!this.dom) { - throw new Error('Not connected'); - } - return await this.dom.hoverBySelector(selector); - } - - async fillBySelector(selector: string, text: string): Promise { - if (!this.dom) { - throw new Error('Not connected'); - } - return await this.dom.fillBySelector(selector, text); - } - - async dragAndDropBySelectors(sourceSelector: string, targetSelector: string): Promise { - if (!this.dom) { - throw new Error('Not connected'); - } - return await this.dom.dragAndDropBySelectors(sourceSelector, targetSelector); - } - - async uploadFileBySelector(selector: string, filePath: string): Promise { - if (!this.dom) { - throw new Error('Not connected'); - } - return await this.dom.uploadFileBySelector(selector, filePath); - } - // UID-based input methods async clickByUid(uid: string, dblClick = false): Promise {