Skip to content
Merged
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
40 changes: 40 additions & 0 deletions docs/src/api/class-frame.md
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,46 @@ Returns the page containing this frame.

Parent frame, if any. Detached frames and main frames return `null`.

## method: Frame.pierceFrames
* since: v1.63
- returns: <[FrameLocator]>

When working with iframes, you can create a frame locator that will search for elements in the main frame
and in all iframes on the page, so that you don't need to locate each iframe first.

Note that all elements matching the locator must belong to a single frame. For example, if the page contains
two iframes, each with a `Submit` button, piercing frames and locating a button will throw an error
because it matches elements from multiple frames.

**Usage**

Following snippet locates a button, either in the main frame or in one of the iframes:

```js
const locator = frame.pierceFrames().getByRole('button');
await locator.click();
```

```java
Locator locator = frame.pierceFrames().getByRole(AriaRole.BUTTON);
locator.click();
```

```python async
locator = frame.pierce_frames.get_by_role("button")
await locator.click()
```

```python sync
locator = frame.pierce_frames.get_by_role("button")
locator.click()
```

```csharp
var locator = frame.PierceFrames.GetByRole(AriaRole.Button);
await locator.ClickAsync();
```

## async method: Frame.press
* since: v1.8
* discouraged: Use locator-based [`method: Locator.press`] instead. Read more about [locators](../locators.md).
Expand Down
44 changes: 42 additions & 2 deletions docs/src/api/class-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -2358,7 +2358,7 @@ last redirect. If cannot go back, returns `null`.
Navigate to the previous page in history.

:::warning
**Testing Back/Forward Cache (BFCache) is not supported.**
**Testing Back/Forward Cache (BFCache) is not supported.**
By default, Playwright disables the Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using `page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized `Page` state.
:::

Expand All @@ -2383,7 +2383,7 @@ last redirect. If cannot go forward, returns `null`.
Navigate to the next page in history.

:::warning
**Testing Back/Forward Cache (BFCache) is not supported.**
**Testing Back/Forward Cache (BFCache) is not supported.**
By default, Playwright disables the Back/Forward Cache across all browsers. Even if explicitly enabled, Playwright's internal state relies on network-level navigation events. Because BFCache restores unfreeze the DOM without firing these events, using `page.goBack()` or `page.goForward()` to trigger a BFCache restore will result in timeouts and a desynchronized `Page` state.
:::

Expand Down Expand Up @@ -3162,6 +3162,46 @@ var locator = await page.PickLocatorAsync();
Console.WriteLine(locator);
```

## method: Page.pierceFrames
* since: v1.63
- returns: <[FrameLocator]>

When working with iframes, you can create a frame locator that will search for elements in the main frame
and in all iframes on the page, so that you don't need to locate each iframe first.

Note that all elements matching the locator must belong to a single frame. For example, if the page contains
two iframes, each with a `Submit` button, piercing frames and locating a button will throw an error
because it matches elements from multiple frames.

**Usage**

Following snippet locates a button, either in the main frame or in one of the iframes:

```js
const locator = page.pierceFrames().getByRole('button');
await locator.click();
```

```java
Locator locator = page.pierceFrames().getByRole(AriaRole.BUTTON);
locator.click();
```

```python async
locator = page.pierce_frames.get_by_role("button")
await locator.click()
```

```python sync
locator = page.pierce_frames.get_by_role("button")
locator.click()
```

```csharp
var locator = page.PierceFrames.GetByRole(AriaRole.Button);
await locator.ClickAsync();
```

## async method: Page.press
* since: v1.8
* discouraged: Use locator-based [`method: Locator.press`] instead. Read more about [locators](../locators.md).
Expand Down
49 changes: 0 additions & 49 deletions packages/injected/src/recorder/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ class InspectTool implements RecorderTool {
void this._recorder.recordAction({
name: 'assertVisible',
selector,
signals: [],
});
this._recorder.setMode('recording');
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
Expand Down Expand Up @@ -269,39 +268,14 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: checkbox.checked ? 'check' : 'uncheck',
selector: this._hoveredModel?.selector ?? this._selectorForElement(target),
signals: [],
}, { autoExpect: true });
return;
}

// Only single clicks are recorded here; double clicks are recorded in onDblClick.
if (event.detail !== 1)
return;
this._recordAction({
name: 'click',
selector: this._hoveredModel?.selector ?? this._selectorForElement(target),
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail
}, { autoExpect: true });
}

onDblClick(event: MouseEvent) {
if (this._dialog.isShowing())
return;
if (isRangeInput(this._hoveredElement))
return;
if (this._shouldIgnoreMouseEvent(event))
return;

const target = this._recorder.deepEventTarget(event);
this._recordAction({
name: 'click',
selector: this._hoveredModel?.selector ?? this._selectorForElement(target),
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail
Expand All @@ -324,7 +298,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: this._hoveredModel?.selector ?? this._selectorForElement(target),
position: positionForEvent(event),
signals: [],
button: 'right',
modifiers: modifiersForEvent(event),
clickCount: 1,
Expand Down Expand Up @@ -409,7 +382,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: 'setInputFiles',
selector,
signals: [],
files: [...((target as HTMLInputElement).files || [])].map(file => file.name),
});
return;
Expand All @@ -420,7 +392,6 @@ class RecordActionTool implements RecorderTool {
name: 'fill',
// must use hoveredModel instead of activeModel for it to work in webkit
selector: this._hoveredModel?.selector ?? this._selectorForElement(target),
signals: [],
text: target.value,
});
return;
Expand All @@ -435,7 +406,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: 'fill',
selector: this._activeSelectorForEvent(event),
signals: [],
text: target.isContentEditable ? target.innerText : (target as HTMLInputElement).value,
});
}
Expand All @@ -446,7 +416,6 @@ class RecordActionTool implements RecorderTool {
name: 'select',
selector: this._activeSelectorForEvent(event),
options: [...selectElement.selectedOptions].map(option => option.value),
signals: []
});
}
}
Expand All @@ -463,7 +432,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: checkbox.checked ? 'uncheck' : 'check',
selector: this._activeSelectorForEvent(event),
signals: [],
}, { autoExpect: true });
return;
}
Expand All @@ -472,7 +440,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: 'press',
selector: this._activeSelectorForEvent(event),
signals: [],
key: event.key,
modifiers: modifiersForEvent(event),
}, { autoExpect: true });
Expand All @@ -498,7 +465,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'left',
modifiers: 0,
clickCount: 1,
Expand All @@ -510,7 +476,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'right',
modifiers: 0,
clickCount: 1,
Expand All @@ -522,7 +487,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'left',
modifiers: 0,
clickCount: 2,
Expand All @@ -534,7 +498,6 @@ class RecordActionTool implements RecorderTool {
name: 'hover',
selector: model.selector,
position: actionPosition,
signals: [],
}),
},
{
Expand Down Expand Up @@ -731,7 +694,6 @@ class JsonRecordActionTool implements RecorderTool {
name: checkbox.checked ? 'check' : 'uncheck',
selector,
ref,
signals: [],
ariaSnapshot,
});
return;
Expand All @@ -743,7 +705,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
ariaSnapshot,
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail,
Expand All @@ -759,7 +720,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
ariaSnapshot,
position: positionForEvent(event),
signals: [],
button: 'right',
modifiers: modifiersForEvent(event),
clickCount: 1,
Expand All @@ -776,7 +736,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
text: element.value,
});
return;
Expand All @@ -793,7 +752,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
selector,
ariaSnapshot,
signals: [],
text: element.isContentEditable ? element.innerText : (element as HTMLInputElement).value,
});
return;
Expand All @@ -807,7 +765,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
ariaSnapshot,
options: [...selectElement.selectedOptions].map(option => option.value),
signals: []
});
return;
}
Expand All @@ -829,7 +786,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
});
return;
}
Expand All @@ -840,7 +796,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
key: event.key,
modifiers: modifiersForEvent(event),
});
Expand Down Expand Up @@ -992,15 +947,13 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertChecked',
selector,
signals: [],
// Interestingly, inputElement.checked is reversed inside this event handler.
checked: !(target as HTMLInputElement).checked,
};
} else {
return {
name: 'assertValue',
selector,
signals: [],
value: (target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).value,
};
}
Expand All @@ -1013,7 +966,6 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertSnapshot',
selector: this._hoverHighlight.selector,
signals: [],
ariaSnapshot: this._recorder.injectedScript.ariaSnapshot(target, { mode: 'codegen' }),
};
} else {
Expand All @@ -1025,7 +977,6 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertText',
selector: this._hoverHighlight.selector,
signals: [],
text: this._recorder.injectedScript.utils.elementText(this._textCache, target).normalized,
substring: true,
};
Expand Down
5 changes: 1 addition & 4 deletions packages/isomorphic/codegen/actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type ActionName =

export type ActionBase = {
name: ActionName,
signals: Signal[],
ariaSnapshot?: string,
};

Expand Down Expand Up @@ -166,12 +165,10 @@ export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSig
export type ActionInContext = {
pageGuid: string;
action: Action;
startTime: number;
endTime?: number;
signals: Signal[];
};

export type SignalInContext = {
pageGuid: string;
signal: Signal;
timestamp: number;
};
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext);

if (signals.dialog) {
formatter.add(` void ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler(object sender, IDialog dialog)
Expand Down
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext);

if (signals.dialog) {
formatter.add(` ${pageAlias}.onceDialog(dialog -> {
Expand Down
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext);

if (signals.dialog) {
formatter.add(` ${pageAlias}.once('dialog', dialog => {
Expand Down
Loading
Loading