Skip to content

Commit

Permalink
feat(ctrl_or_meta): add a universal ctrl-meta modifier
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Apr 26, 2024
1 parent b5dee9e commit 1bdd5cf
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 83 deletions.
2 changes: 1 addition & 1 deletion docs/src/api/params.md
Expand Up @@ -97,7 +97,7 @@ 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.
Expand Down
16 changes: 16 additions & 0 deletions docs/src/input.md
Expand Up @@ -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();

Expand All @@ -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();

Expand All @@ -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()

Expand Down Expand Up @@ -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();

Expand Down
16 changes: 8 additions & 8 deletions packages/playwright-core/src/protocol/validator.ts
Expand Up @@ -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'])),
Expand Down Expand Up @@ -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'])),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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'])),
Expand All @@ -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'])),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/dom.ts
Expand Up @@ -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();
Expand Down
20 changes: 15 additions & 5 deletions packages/playwright-core/src/server/input.ts
Expand Up @@ -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) {
Expand All @@ -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');
Expand Down Expand Up @@ -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);
Expand All @@ -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>;
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/page.ts
Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions packages/playwright-core/src/server/recorder/utils.ts
Expand Up @@ -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];
Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/types.ts
Expand Up @@ -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;
};

Expand Down
32 changes: 16 additions & 16 deletions packages/playwright-core/types/types.d.ts
Expand Up @@ -2066,7 +2066,7 @@ 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.
*/
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
Expand Down Expand Up @@ -2181,7 +2181,7 @@ 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.
*/
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
Expand Down Expand Up @@ -3071,7 +3071,7 @@ 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.
*/
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
Expand Down Expand Up @@ -4185,7 +4185,7 @@ 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.
*/
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
Expand Down Expand Up @@ -5761,7 +5761,7 @@ 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.
*/
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
Expand Down Expand Up @@ -5848,7 +5848,7 @@ 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.
*/
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
Expand Down Expand Up @@ -6541,7 +6541,7 @@ 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.
*/
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
Expand Down Expand Up @@ -7231,7 +7231,7 @@ 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.
*/
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
Expand Down Expand Up @@ -9840,7 +9840,7 @@ 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.
*/
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
Expand Down Expand Up @@ -9919,7 +9919,7 @@ 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.
*/
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
Expand Down Expand Up @@ -10080,7 +10080,7 @@ 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.
*/
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
Expand Down Expand Up @@ -10637,7 +10637,7 @@ 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.
*/
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
Expand Down Expand Up @@ -11285,7 +11285,7 @@ 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.
*/
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
Expand Down Expand Up @@ -11397,7 +11397,7 @@ 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.
*/
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
Expand Down Expand Up @@ -12070,7 +12070,7 @@ 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.
*/
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
Expand Down Expand Up @@ -12866,7 +12866,7 @@ 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.
*/
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
Expand Down

0 comments on commit 1bdd5cf

Please sign in to comment.