feat(ctrl_or_meta): add a universal ctrl-meta modifier (#30572)

Fixes https://github.com/microsoft/playwright/issues/12168
This commit is contained in:
Pavel Feldman 2024-04-29 08:15:12 -07:00 committed by GitHub
parent ebafb95054
commit 96f3d19819
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 171 additions and 112 deletions

View file

@ -692,7 +692,7 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.

View file

@ -1394,7 +1394,8 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
`ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.

View file

@ -151,7 +151,8 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
`ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.
@ -227,7 +228,8 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
`ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.

View file

@ -1761,7 +1761,8 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
`ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.

View file

@ -3013,7 +3013,8 @@ generate the text for. A superset of the [`param: key`] values can be found
`F1` - `F12`, `Digit0`- `Digit9`, `KeyA`- `KeyZ`, `Backquote`, `Minus`, `Equal`, `Backslash`, `Backspace`, `Tab`,
`Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`, etc.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`, `ControlOrMeta`.
`ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
Holding down `Shift` will type the text that corresponds to the [`param: key`] in the upper case.

View file

@ -97,10 +97,11 @@ A point to use relative to the top-left corner of element padding box. If not sp
element.
## input-modifiers
- `modifiers` <[Array]<[KeyboardModifier]<"Alt"|"Control"|"Meta"|"Shift">>>
- `modifiers` <[Array]<[KeyboardModifier]<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">>>
Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores current
modifiers back. If not specified, currently pressed modifiers are used.
modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to "Control" on Windows
and Linux and to "Meta" on macOS.
## input-button
- `button` <[MouseButton]<"left"|"right"|"middle">>

View file

@ -217,6 +217,10 @@ await page.getByText('Item').click({ button: 'right' });
// Shift + click
await page.getByText('Item').click({ modifiers: ['Shift'] });
// Ctrl + click or Windows and Linux
// Meta + click on macOS
await page.getByText('Item').click({ modifiers: ['ControlOrMeta'] });
// Hover over element
await page.getByText('Item').hover();
@ -237,6 +241,10 @@ page.getByText("Item").click(new Locator.ClickOptions().setButton(MouseButton.RI
// Shift + click
page.getByText("Item").click(new Locator.ClickOptions().setModifiers(Arrays.asList(KeyboardModifier.SHIFT)));
// Ctrl + click or Windows and Linux
// Meta + click on macOS
page.getByText("Item").click(new Locator.ClickOptions().setModifiers(Arrays.asList(KeyboardModifier.CONTROL_OR_META)));
// Hover over element
page.getByText("Item").hover();
@ -257,6 +265,10 @@ await page.get_by_text("Item").click(button="right")
# Shift + click
await page.get_by_text("Item").click(modifiers=["Shift"])
# Ctrl + click or Windows and Linux
# Meta + click on macOS
await page.get_by_text("Item").click(modifiers=["ControlOrMeta"])
# Hover over element
await page.get_by_text("Item").hover()
@ -297,6 +309,10 @@ await page.GetByText("Item").ClickAsync(new() { Button = MouseButton.Right });
// Shift + click
await page.GetByText("Item").ClickAsync(new() { Modifiers = new[] { KeyboardModifier.Shift } });
// Ctrl + click or Windows and Linux
// Meta + click on macOS
await page.GetByText("Item").ClickAsync(new() { Modifiers = new[] { KeyboardModifier.ControlOrMeta } });
// Hover over element
await page.GetByText("Item").HoverAsync();

View file

@ -1342,7 +1342,7 @@ scheme.FrameClickParams = tObject({
strict: tOptional(tBoolean),
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
delay: tOptional(tNumber),
button: tOptional(tEnum(['left', 'right', 'middle'])),
@ -1372,7 +1372,7 @@ scheme.FrameDblclickParams = tObject({
strict: tOptional(tBoolean),
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
delay: tOptional(tNumber),
button: tOptional(tEnum(['left', 'right', 'middle'])),
@ -1450,7 +1450,7 @@ scheme.FrameHoverParams = tObject({
selector: tString,
strict: tOptional(tBoolean),
force: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
timeout: tOptional(tNumber),
trial: tOptional(tBoolean),
@ -1597,7 +1597,7 @@ scheme.FrameTapParams = tObject({
strict: tOptional(tBoolean),
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
timeout: tOptional(tNumber),
trial: tOptional(tBoolean),
@ -1792,7 +1792,7 @@ scheme.ElementHandleCheckResult = tOptional(tObject({}));
scheme.ElementHandleClickParams = tObject({
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
delay: tOptional(tNumber),
button: tOptional(tEnum(['left', 'right', 'middle'])),
@ -1808,7 +1808,7 @@ scheme.ElementHandleContentFrameResult = tObject({
scheme.ElementHandleDblclickParams = tObject({
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
delay: tOptional(tNumber),
button: tOptional(tEnum(['left', 'right', 'middle'])),
@ -1838,7 +1838,7 @@ scheme.ElementHandleGetAttributeResult = tObject({
});
scheme.ElementHandleHoverParams = tObject({
force: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
timeout: tOptional(tNumber),
trial: tOptional(tBoolean),
@ -1962,7 +1962,7 @@ scheme.ElementHandleSetInputFilesResult = tOptional(tObject({}));
scheme.ElementHandleTapParams = tObject({
force: tOptional(tBoolean),
noWaitAfter: tOptional(tBoolean),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'Meta', 'Shift']))),
modifiers: tOptional(tArray(tEnum(['Alt', 'Control', 'ControlOrMeta', 'Meta', 'Shift']))),
position: tOptional(tType('Point')),
timeout: tOptional(tNumber),
trial: tOptional(tBoolean),

View file

@ -449,11 +449,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
progress.throwIfAborted(); // Avoid action that has side-effects.
let restoreModifiers: types.KeyboardModifier[] | undefined;
if (options && options.modifiers)
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
restoreModifiers = await this._page.keyboard.ensureModifiers(options.modifiers);
progress.log(` performing ${actionName} action`);
await action(point);
if (restoreModifiers)
await this._page.keyboard._ensureModifiers(restoreModifiers);
await this._page.keyboard.ensureModifiers(restoreModifiers);
if (hitTargetInterceptionHandle) {
const stopHitTargetInterception = hitTargetInterceptionHandle.evaluate(h => h.stop()).catch(e => 'done' as const).finally(() => {
hitTargetInterceptionHandle?.dispose();

View file

@ -44,11 +44,9 @@ export class Keyboard {
private _pressedModifiers = new Set<types.KeyboardModifier>();
private _pressedKeys = new Set<string>();
private _raw: RawKeyboard;
private _page: Page;
constructor(raw: RawKeyboard, page: Page) {
constructor(raw: RawKeyboard) {
this._raw = raw;
this._page = page;
}
async down(key: string) {
@ -61,7 +59,8 @@ export class Keyboard {
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
}
private _keyDescriptionForString(keyString: string): KeyDescription {
private _keyDescriptionForString(str: string): KeyDescription {
const keyString = resolveSmartModifierString(str);
let description = usKeyboardLayout.get(keyString);
assert(description, `Unknown key: "${keyString}"`);
const shift = this._pressedModifiers.has('Shift');
@ -126,7 +125,8 @@ export class Keyboard {
await this.up(tokens[i]);
}
async _ensureModifiers(modifiers: types.KeyboardModifier[]): Promise<types.KeyboardModifier[]> {
async ensureModifiers(mm: types.SmartKeyboardModifier[]): Promise<types.KeyboardModifier[]> {
const modifiers = mm.map(resolveSmartModifier);
for (const modifier of modifiers) {
if (!kModifiers.includes(modifier))
throw new Error('Unknown modifier ' + modifier);
@ -148,6 +148,16 @@ export class Keyboard {
}
}
export function resolveSmartModifierString(key: string): string {
if (key === 'ControlOrMeta')
return process.platform === 'darwin' ? 'Meta' : 'Control';
return key;
}
export function resolveSmartModifier(m: types.SmartKeyboardModifier): types.KeyboardModifier {
return resolveSmartModifierString(m) as types.KeyboardModifier;
}
export interface RawMouse {
move(x: number, y: number, button: types.MouseButton | 'none', buttons: Set<types.MouseButton>, modifiers: Set<types.KeyboardModifier>, forClick: boolean): Promise<void>;
down(x: number, y: number, button: types.MouseButton, buttons: Set<types.MouseButton>, modifiers: Set<types.KeyboardModifier>, clickCount: number): Promise<void>;

View file

@ -183,7 +183,7 @@ export class Page extends SdkObject {
this._delegate = delegate;
this._browserContext = browserContext;
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
this.keyboard = new input.Keyboard(delegate.rawKeyboard, this);
this.keyboard = new input.Keyboard(delegate.rawKeyboard);
this.mouse = new input.Mouse(delegate.rawMouse, this);
this.touchscreen = new input.Touchscreen(delegate.rawTouchscreen, this);
this._timeoutSettings = new TimeoutSettings(browserContext._timeoutSettings);

View file

@ -15,6 +15,7 @@
*/
import type { Frame } from '../frames';
import type { SmartKeyboardModifier } from '../types';
import type * as actions from './recorderActions';
export type MouseClickOptions = Parameters<Frame['click']>[2];
@ -36,14 +37,14 @@ export function toClickOptions(action: actions.ClickAction): { method: 'click' |
return { method, options };
}
export function toModifiers(modifiers: number): ('Alt' | 'Control' | 'Meta' | 'Shift')[] {
const result: ('Alt' | 'Control' | 'Meta' | 'Shift')[] = [];
export function toModifiers(modifiers: number): SmartKeyboardModifier[] {
const result: SmartKeyboardModifier[] = [];
if (modifiers & 1)
result.push('Alt');
if (modifiers & 2)
result.push('Control');
result.push('ControlOrMeta');
if (modifiers & 4)
result.push('Meta');
result.push('ControlOrMeta');
if (modifiers & 8)
result.push('Shift');
return result;

View file

@ -105,10 +105,11 @@ export type ProxySettings = {
};
export type KeyboardModifier = 'Alt' | 'Control' | 'Meta' | 'Shift';
export type SmartKeyboardModifier = KeyboardModifier | 'ControlOrMeta';
export type MouseButton = 'left' | 'right' | 'middle';
export type PointerActionOptions = {
modifiers?: KeyboardModifier[];
modifiers?: SmartKeyboardModifier[];
position?: Point;
};

View file

@ -2064,9 +2064,10 @@ export interface Page {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -2179,9 +2180,10 @@ export interface Page {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -3069,9 +3071,10 @@ export interface Page {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -3591,7 +3594,8 @@ export interface Page {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*
@ -4183,9 +4187,10 @@ export interface Page {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -5759,9 +5764,10 @@ export interface Frame {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -5846,9 +5852,10 @@ export interface Frame {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -6539,9 +6546,10 @@ export interface Frame {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -6897,7 +6905,8 @@ export interface Frame {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*
@ -7229,9 +7238,10 @@ export interface Frame {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -9838,9 +9848,10 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -9917,9 +9928,10 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -10078,9 +10090,10 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -10224,7 +10237,8 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*
@ -10635,9 +10649,10 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -11283,9 +11298,10 @@ export interface Locator {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -11395,9 +11411,10 @@ export interface Locator {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -12068,9 +12085,10 @@ export interface Locator {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -12437,7 +12455,8 @@ export interface Locator {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*
@ -12864,9 +12883,10 @@ export interface Locator {
/**
* Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores
* current modifiers back. If not specified, currently pressed modifiers are used.
* current modifiers back. If not specified, currently pressed modifiers are used. "ControlOrMeta" resolves to
* "Control" on Windows and Linux and to "Meta" on macOS.
*/
modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">;
modifiers?: Array<"Alt"|"Control"|"ControlOrMeta"|"Meta"|"Shift">;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You
@ -18316,7 +18336,8 @@ export interface Keyboard {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*
@ -18365,7 +18386,8 @@ export interface Keyboard {
* `Delete`, `Escape`, `ArrowDown`, `End`, `Enter`, `Home`, `Insert`, `PageDown`, `PageUp`, `ArrowRight`, `ArrowUp`,
* etc.
*
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`.
* Following modification shortcuts are also supported: `Shift`, `Control`, `Alt`, `Meta`, `ShiftLeft`,
* `ControlOrMeta`. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS.
*
* Holding down `Shift` will type the text that corresponds to the `key` in the upper case.
*

View file

@ -2458,7 +2458,7 @@ export type FrameClickParams = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -2470,7 +2470,7 @@ export type FrameClickOptions = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -2510,7 +2510,7 @@ export type FrameDblclickParams = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -2521,7 +2521,7 @@ export type FrameDblclickOptions = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -2633,7 +2633,7 @@ export type FrameHoverParams = {
selector: string,
strict?: boolean,
force?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -2642,7 +2642,7 @@ export type FrameHoverParams = {
export type FrameHoverOptions = {
strict?: boolean,
force?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -2867,7 +2867,7 @@ export type FrameTapParams = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -2876,7 +2876,7 @@ export type FrameTapOptions = {
strict?: boolean,
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -3210,7 +3210,7 @@ export type ElementHandleCheckResult = void;
export type ElementHandleClickParams = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -3221,7 +3221,7 @@ export type ElementHandleClickParams = {
export type ElementHandleClickOptions = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -3238,7 +3238,7 @@ export type ElementHandleContentFrameResult = {
export type ElementHandleDblclickParams = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -3248,7 +3248,7 @@ export type ElementHandleDblclickParams = {
export type ElementHandleDblclickOptions = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
delay?: number,
button?: 'left' | 'right' | 'middle',
@ -3290,7 +3290,7 @@ export type ElementHandleGetAttributeResult = {
};
export type ElementHandleHoverParams = {
force?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -3298,7 +3298,7 @@ export type ElementHandleHoverParams = {
};
export type ElementHandleHoverOptions = {
force?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -3488,7 +3488,7 @@ export type ElementHandleSetInputFilesResult = void;
export type ElementHandleTapParams = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,
@ -3496,7 +3496,7 @@ export type ElementHandleTapParams = {
export type ElementHandleTapOptions = {
force?: boolean,
noWaitAfter?: boolean,
modifiers?: ('Alt' | 'Control' | 'Meta' | 'Shift')[],
modifiers?: ('Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift')[],
position?: Point,
timeout?: number,
trial?: boolean,

View file

@ -1791,6 +1791,7 @@ Frame:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -1842,6 +1843,7 @@ Frame:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -1956,6 +1958,7 @@ Frame:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -2164,6 +2167,7 @@ Frame:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -2442,6 +2446,7 @@ ElementHandle:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -2475,6 +2480,7 @@ ElementHandle:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -2532,6 +2538,7 @@ ElementHandle:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?
@ -2718,6 +2725,7 @@ ElementHandle:
literals:
- Alt
- Control
- ControlOrMeta
- Meta
- Shift
position: Point?

View file

@ -170,7 +170,7 @@ it('should work with Shift-clicking', async ({ browser, server, browserName }) =
await context.close();
});
it('should work with Ctrl-clicking', async ({ browser, server, isMac, browserName }) => {
it('should work with Ctrl-clicking', async ({ browser, server, browserName }) => {
it.fixme(browserName === 'firefox', 'Reports an opener in this case.');
const context = await browser.newContext();
@ -179,13 +179,13 @@ it('should work with Ctrl-clicking', async ({ browser, server, isMac, browserNam
await page.setContent('<a href="/one-style.html">yo</a>');
const [popup] = await Promise.all([
context.waitForEvent('page'),
page.click('a', { modifiers: [isMac ? 'Meta' : 'Control'] }),
page.click('a', { modifiers: ['ControlOrMeta'] }),
]);
expect(await popup.opener()).toBe(null);
await context.close();
});
it('should not hang on ctrl-click during provisional load', async ({ context, page, server, isMac, isWindows, browserName, isLinux }) => {
it('should not hang on ctrl-click during provisional load', async ({ context, page, server, isWindows, browserName, isLinux }) => {
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/11595' });
it.skip(browserName === 'chromium', 'Chromium does not dispatch renderer messages while navigation is provisional.');
it.fixme(browserName === 'webkit' && isWindows, 'Timesout while trying to click');
@ -195,7 +195,7 @@ it('should not hang on ctrl-click during provisional load', async ({ context, pa
server.setRoute('/slow.html', () => {});
const [popup] = await Promise.all([
context.waitForEvent('page'),
server.waitForRequest('/slow.html').then(() => page.click('a', { modifiers: [isMac ? 'Meta' : 'Control'] })),
server.waitForRequest('/slow.html').then(() => page.click('a', { modifiers: ['ControlOrMeta'] })),
page.evaluate(url => setTimeout(() => location.href = url, 0), server.CROSS_PROCESS_PREFIX + '/slow.html'),
]);
expect(popup).toBeTruthy();

View file

@ -337,14 +337,14 @@ await page.GetByRole(AriaRole.Button, new() { Name = "click me" }).ClickAsync();
}
});
test('should record open in a new tab with url', async ({ page, openRecorder, browserName, platform }) => {
test('should record open in a new tab with url', async ({ page, openRecorder, browserName }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
const locator = await recorder.hoverOverElement('a');
expect(locator).toBe(`getByRole('link', { name: 'link' })`);
await page.click('a', { modifiers: [platform === 'darwin' ? 'Meta' : 'Control'] });
await page.click('a', { modifiers: ['ControlOrMeta'] });
const sources = await recorder.waitForOutput('JavaScript', 'page1');
if (browserName !== 'firefox') {
@ -361,7 +361,7 @@ await page1.GotoAsync("about:blank?foo");`);
expect(sources.get('JavaScript')!.text).toContain(`
const page1Promise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'link' }).click({
modifiers: ['${platform === 'darwin' ? 'Meta' : 'Control'}']
modifiers: ['ControlOrMeta']
});
const page1 = await page1Promise;`);
}

View file

@ -318,10 +318,9 @@ it('should handle selectAll', async ({ page, server, isMac }) => {
await page.goto(server.PREFIX + '/input/textarea.html');
const textarea = await page.$('textarea');
await textarea.type('some text');
const modifier = isMac ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.down('ControlOrMeta');
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.up('ControlOrMeta');
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('');
});
@ -346,10 +345,9 @@ it('should be able to prevent selectAll', async ({ page, server, isMac }) => {
event.preventDefault();
}, false);
});
const modifier = isMac ? 'Meta' : 'Control';
await page.keyboard.down(modifier);
await page.keyboard.down('ControlOrMeta');
await page.keyboard.press('a');
await page.keyboard.up(modifier);
await page.keyboard.up('ControlOrMeta');
await page.keyboard.press('Backspace');
expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some tex');
});
@ -469,39 +467,36 @@ it('should dispatch a click event on a button when Enter gets pressed', async ({
expect((await actual.jsonValue()).clicked).toBe(true);
});
it('should support simple copy-pasting', async ({ page, isMac, browserName }) => {
const modifier = isMac ? 'Meta' : 'Control';
it('should support simple copy-pasting', async ({ page }) => {
await page.setContent(`<div contenteditable>123</div>`);
await page.focus('div');
await page.keyboard.press(`${modifier}+KeyA`);
await page.keyboard.press(`${modifier}+KeyC`);
await page.keyboard.press(`${modifier}+KeyV`);
await page.keyboard.press(`${modifier}+KeyV`);
await page.keyboard.press(`ControlOrMeta+KeyA`);
await page.keyboard.press(`ControlOrMeta+KeyC`);
await page.keyboard.press(`ControlOrMeta+KeyV`);
await page.keyboard.press(`ControlOrMeta+KeyV`);
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('123123');
});
it('should support simple cut-pasting', async ({ page, isMac }) => {
const modifier = isMac ? 'Meta' : 'Control';
it('should support simple cut-pasting', async ({ page }) => {
await page.setContent(`<div contenteditable>123</div>`);
await page.focus('div');
await page.keyboard.press(`${modifier}+KeyA`);
await page.keyboard.press(`${modifier}+KeyX`);
await page.keyboard.press(`${modifier}+KeyV`);
await page.keyboard.press(`${modifier}+KeyV`);
await page.keyboard.press(`ControlOrMeta+KeyA`);
await page.keyboard.press(`ControlOrMeta+KeyX`);
await page.keyboard.press(`ControlOrMeta+KeyV`);
await page.keyboard.press(`ControlOrMeta+KeyV`);
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('123123');
});
it('should support undo-redo', async ({ page, isMac, browserName, isLinux }) => {
it('should support undo-redo', async ({ page, browserName, isLinux }) => {
it.fixme(browserName === 'webkit' && isLinux, 'https://github.com/microsoft/playwright/issues/12000');
const modifier = isMac ? 'Meta' : 'Control';
await page.setContent(`<div contenteditable></div>`);
const div = page.locator('div');
await expect(div).toHaveText('');
await div.type('123');
await expect(div).toHaveText('123');
await page.keyboard.press(`${modifier}+KeyZ`);
await page.keyboard.press(`ControlOrMeta+KeyZ`);
await expect(div).toHaveText('');
await page.keyboard.press(`Shift+${modifier}+KeyZ`);
await page.keyboard.press(`Shift+ControlOrMeta+KeyZ`);
await expect(div).toHaveText('123');
});