Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions packages/core/src/locators/locator-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,12 @@ function getSimpleSuggestedLocators(
element: JSONElement,
ctx: LocatorContext,
automationName: string,
inUiAutomatorScope: boolean,
targetNode?: XMLNode
): [LocatorStrategy, string][] {
const results: [LocatorStrategy, string][] = []
const isAndroid = automationName.toLowerCase().includes('uiautomator')
const attrs = element.attributes
const inUiAutomatorScope = isAndroid
? isInUiAutomatorScope(element, ctx.parsedDOM)
: true

if (isAndroid) {
// Resource ID
Expand Down Expand Up @@ -526,13 +524,11 @@ function getComplexSuggestedLocators(
element: JSONElement,
ctx: LocatorContext,
automationName: string,
inUiAutomatorScope: boolean,
targetNode?: XMLNode
): [LocatorStrategy, string][] {
const results: [LocatorStrategy, string][] = []
const isAndroid = automationName.toLowerCase().includes('uiautomator')
const inUiAutomatorScope = isAndroid
? isInUiAutomatorScope(element, ctx.parsedDOM)
: true

if (isAndroid) {
if (inUiAutomatorScope) {
Expand Down Expand Up @@ -594,16 +590,22 @@ export function getSuggestedLocators(
isAndroid: automationName.toLowerCase().includes('uiautomator')
}

const inUiAutomatorScope = locatorCtx.isAndroid
? isInUiAutomatorScope(element, locatorCtx.parsedDOM)
: true

const simpleLocators = getSimpleSuggestedLocators(
element,
locatorCtx,
automationName,
inUiAutomatorScope,
targetNode
)
const complexLocators = getComplexSuggestedLocators(
element,
locatorCtx,
automationName,
inUiAutomatorScope,
targetNode
)

Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/session-capturer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,17 +455,17 @@ export abstract class SessionCapturerBase {

// ── Hooks (subclasses override) ─────────────────────────────────────────
/**
* Default: forward a single ConsoleLog via the `consoleLogs` scope.
* Args is passed as an array (matching the original console.* call shape:
* `console.log('a', 'b')` → `args = ['a', 'b']`) so subclasses can preserve
* the multi-argument structure for the UI.
* Build a ConsoleLog entry from the captured line, push it into the local
* `consoleLogs` array (so it ends up in any future trace export), and
* broadcast it live via the `consoleLogs` WS scope.
*
* Subclasses that need to maintain local capture state (for the rerun/
* replay flow) should override to also push the entry into their own
* array — see service's onLine override.
* Args is passed as an array (matching the original console.* call shape:
* `console.log('a', 'b')` → `args = ['a', 'b']`) so the multi-argument
* structure is preserved for the UI.
*/
protected onLine(type: LogLevel, args: string[], source: LogSource): void {
const entry = createConsoleLogEntry(type, args, source)
this.consoleLogs.push(entry)
this.sendUpstream('consoleLogs', [entry])
}

Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/trace-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ function buildTraceBundle(
const pageId = `page@${idPrefix}`
const viewport = trace.metadata.viewport ?? { width: 1280, height: 720 }
const snapshots = trace.actionSnapshots ?? []
const events: TraceEvent[] = [buildContextOptions(trace, contextId, wallTime)]
const ctxOptions = buildContextOptions(trace, contextId, wallTime)
const events: TraceEvent[] = [ctxOptions]

// Emit initial screencast-frame (timestamp=0) using the first snapshot's
// resources so trace viewers show the page state before any interaction.
Expand Down Expand Up @@ -438,10 +439,7 @@ function buildTraceBundle(
...buildActionEvents(trace.commands, pageId, wallTime)
)
events.sort(compareEvents)
const caps = trace.metadata.capabilities as
| Record<string, unknown>
| undefined
const ctxBName = resolveContextNaming(caps).title
const ctxBName = ctxOptions.title
return {
traceNdjson: events.map((e) => JSON.stringify(e)).join('\n') + '\n',
networkNdjson: buildNetworkNdjson(trace.networkRequests, wallTime, pageId),
Expand Down
7 changes: 0 additions & 7 deletions packages/elements/.npmignore

This file was deleted.

145 changes: 145 additions & 0 deletions packages/elements/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# @wdio/elements

Element detection and locator generation for WebdriverIO — browser DOM querying, mobile page-source parsing, accessibility tree extraction, and AI-readable snapshots.

## Install

```bash
npm install @wdio/elements
```

Requires `webdriverio` as a peer dependency (^9.0.0).

## Usage

### Unified entry point (auto-detects browser vs mobile)

```ts
import { getElements } from '@wdio/elements'

const result = await getElements(browser, { limit: 50, inViewportOnly: true })
// { total, showing, hasMore, elements, tree? }
```

### Browser

```ts
import { getInteractableBrowserElements } from '@wdio/elements'

const elements = await getInteractableBrowserElements(browser, {
includeBounds: true,
inViewportOnly: true
})
```

```ts
import { getBrowserAccessibilityTree } from '@wdio/elements'

const nodes = await getBrowserAccessibilityTree(browser, { inViewportOnly: true })
```

### Mobile

```ts
import { getMobileVisibleElements } from '@wdio/elements'

const elements = await getMobileVisibleElements(browser, 'ios', {
includeBounds: true,
includeContainers: false
})
```

For the raw JSON element tree alongside the flat list:

```ts
import { getMobileVisibleElementsWithTree } from '@wdio/elements'

const { elements, tree } = await getMobileVisibleElementsWithTree(browser, 'android')
```

### AI-readable snapshots

```ts
import { serializeWebSnapshot, serializeMobileSnapshot } from '@wdio/elements'

const snapshot = await serializeWebSnapshot(browser, { includeBounds: true })
// or
const snapshot = await serializeMobileSnapshot(browser, 'android', { includeLocators: true })
```

### Locator generation

```ts
import { generateAllElementLocators, xmlToJSON } from '@wdio/elements/locators'

const locators = generateAllElementLocators(pageSource, {
platform: 'android',
viewportSize: { width: 1080, height: 2340 }
})
```

## API

### `getElements(browser, params)`

Auto-detects platform and returns a unified result.

| Param | Type | Default | Description |
|---|---|---|---|
| `inViewportOnly` | `boolean` | `true` | Skip off-screen elements |
| `includeContainers` | `boolean` | `false` | Include layout containers (mobile only) |
| `includeBounds` | `boolean` | `false` | Include element bounding boxes |
| `limit` | `number` | `0` | Max elements (0 = no limit) |
| `offset` | `number` | `0` | Pagination offset |

### `getInteractableBrowserElements(browser, options)`

Single `querySelectorAll` walk — returns flat list of interactable elements.

### `getBrowserAccessibilityTree(browser, options)`

Single DOM walk returning the accessibility tree as a flat `AccessibilityNode[]`.

### `getMobileVisibleElements(browser, platform, options)`

Parses page source XML (2 HTTP calls total) and returns elements with generated locators.

### `getMobileVisibleElementsWithTree(browser, platform, options)`

Same as above but also returns the raw `JSONElement` tree.

### `serializeWebSnapshot(browser, options)` / `serializeMobileSnapshot(browser, platform, options)`

Generate AI-readable (TOON-format) snapshots for LLM consumption.

### `@wdio/elements/locators`

Re-exports the full locator generation pipeline from `@wdio/devtools-core`:

- `xmlToJSON`, `xmlToDOM`, `evaluateXPath`, `checkXPathUniqueness`
- `findDOMNodeByPath`, `parseAndroidBounds`, `parseIOSBounds`
- `flattenElementTree`, `countAttributeOccurrences`, `isAttributeUnique`
- `isInteractableElement`, `isLayoutContainer`, `hasMeaningfulContent`
- `shouldIncludeElement`, `getDefaultFilters`
- `getSuggestedLocators`, `getBestLocator`, `locatorsToObject`
- `generateAllElementLocators`

## Types

```ts
export type {
BrowserElementInfo, GetBrowserElementsOptions,
MobileElementInfo, GetMobileElementsOptions,
AccessibilityNode,
VisibleElementsResult,
WebSnapshotOptions, MobileSnapshotOptions
} from '@wdio/elements'

// From @wdio/elements/locators:
export type {
ElementAttributes, JSONElement, Bounds,
FilterOptions, UniquenessResult,
LocatorStrategy, LocatorContext,
ElementWithLocators, GenerateLocatorsOptions
} from '@wdio/elements/locators'
```
Loading
Loading