Skip to content

Commit ecb4fce

Browse files
committed
chore: include action ref in generated actions
1 parent 472741b commit ecb4fce

File tree

3 files changed

+109
-11
lines changed

3 files changed

+109
-11
lines changed

packages/injected/src/recorder/recorder.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -650,12 +650,13 @@ class JsonRecordActionTool implements RecorderTool {
650650
return;
651651

652652
const checkbox = asCheckbox(element);
653-
const { ariaSnapshot, selector } = this._ariaSnapshot(element);
653+
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
654654
if (checkbox && event.detail === 1) {
655655
// Interestingly, inputElement.checked is reversed inside this event handler.
656656
this._recorder.recordAction({
657657
name: checkbox.checked ? 'check' : 'uncheck',
658658
selector,
659+
ref,
659660
signals: [],
660661
ariaSnapshot,
661662
});
@@ -665,6 +666,7 @@ class JsonRecordActionTool implements RecorderTool {
665666
this._recorder.recordAction({
666667
name: 'click',
667668
selector,
669+
ref,
668670
ariaSnapshot,
669671
position: positionForEvent(event),
670672
signals: [],
@@ -681,27 +683,29 @@ class JsonRecordActionTool implements RecorderTool {
681683
if (this._shouldIgnoreMouseEvent(event))
682684
return;
683685

684-
const { ariaSnapshot, selector } = this._ariaSnapshot(element);
686+
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
685687
this._recorder.recordAction({
686688
name: 'click',
687689
selector,
690+
ref,
688691
ariaSnapshot,
689692
position: positionForEvent(event),
690693
signals: [],
691694
button: buttonForEvent(event),
692695
modifiers: modifiersForEvent(event),
693-
clickCount: event.detail
696+
clickCount: event.detail,
694697
});
695698
}
696699

697700
onInput(event: Event) {
698701
const element = this._recorder.deepEventTarget(event);
699702

700-
const { ariaSnapshot, selector } = this._ariaSnapshot(element);
703+
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
701704
if (isRangeInput(element)) {
702705
this._recorder.recordAction({
703706
name: 'fill',
704707
selector,
708+
ref,
705709
ariaSnapshot,
706710
signals: [],
707711
text: element.value,
@@ -717,6 +721,7 @@ class JsonRecordActionTool implements RecorderTool {
717721

718722
this._recorder.recordAction({
719723
name: 'fill',
724+
ref,
720725
selector,
721726
ariaSnapshot,
722727
signals: [],
@@ -730,6 +735,7 @@ class JsonRecordActionTool implements RecorderTool {
730735
this._recorder.recordAction({
731736
name: 'select',
732737
selector,
738+
ref,
733739
ariaSnapshot,
734740
options: [...selectElement.selectedOptions].map(option => option.value),
735741
signals: []
@@ -743,7 +749,7 @@ class JsonRecordActionTool implements RecorderTool {
743749
return;
744750

745751
const element = this._recorder.deepEventTarget(event);
746-
const { ariaSnapshot, selector } = this._ariaSnapshot(element);
752+
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
747753

748754
// Similarly to click, trigger checkbox on key event, not input.
749755
if (event.key === ' ') {
@@ -752,6 +758,7 @@ class JsonRecordActionTool implements RecorderTool {
752758
this._recorder.recordAction({
753759
name: checkbox.checked ? 'uncheck' : 'check',
754760
selector,
761+
ref,
755762
ariaSnapshot,
756763
signals: [],
757764
});
@@ -762,6 +769,7 @@ class JsonRecordActionTool implements RecorderTool {
762769
this._recorder.recordAction({
763770
name: 'press',
764771
selector,
772+
ref,
765773
ariaSnapshot,
766774
signals: [],
767775
key: event.key,
@@ -819,12 +827,12 @@ class JsonRecordActionTool implements RecorderTool {
819827
return false;
820828
}
821829

822-
private _ariaSnapshot(element: HTMLElement): { ariaSnapshot: string, selector: string };
823-
private _ariaSnapshot(element: HTMLElement | undefined): { ariaSnapshot: string, selector?: string } {
830+
private _ariaSnapshot(element: HTMLElement): { ariaSnapshot: string, selector: string, ref?: string };
831+
private _ariaSnapshot(element: HTMLElement | undefined): { ariaSnapshot: string, selector?: string, ref?: string } {
824832
const { ariaSnapshot, refs } = this._recorder.injectedScript.ariaSnapshotForRecorder();
825833
const ref = element ? refs.get(element) : undefined;
826-
const selector = ref ? `aria-ref=${ref}` : undefined;
827-
return { ariaSnapshot, selector };
834+
const elementInfo = element ? this._recorder.injectedScript.generateSelector(element, { testIdAttributeName: this._recorder.state.testIdAttributeName }) : undefined;
835+
return { ariaSnapshot, selector: elementInfo?.selector, ref };
828836
}
829837
}
830838

packages/recorder/src/actions.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type ActionBase = {
4141

4242
export type ActionWithSelector = ActionBase & {
4343
selector: string,
44+
ref?: string,
4445
};
4546

4647
export type ClickAction = ActionWithSelector & {
@@ -78,9 +79,8 @@ export type ClosesPageAction = ActionBase & {
7879
name: 'closePage',
7980
};
8081

81-
export type PressAction = ActionBase & {
82+
export type PressAction = ActionWithSelector & {
8283
name: 'press',
83-
selector: string,
8484
key: string,
8585
modifiers: number,
8686
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import type { Page } from '@playwright/test';
18+
import type * as actions from '@recorder/actions';
19+
import { test, expect } from './inspectorTest';
20+
import { BrowserContext } from 'playwright-core/src/client/browserContext';
21+
22+
class RecorderLog {
23+
actions: actions.ActionInContext[] = [];
24+
signals: actions.SignalInContext[] = [];
25+
26+
actionAdded(page: Page, actionInContext: actions.ActionInContext): void {
27+
this.actions.push(actionInContext);
28+
}
29+
30+
actionUpdated(page: Page, actionInContext: actions.ActionInContext): void {
31+
this.actions.push(actionInContext);
32+
}
33+
34+
signalAdded(page: Page, signal: actions.SignalInContext): void {
35+
this.signals.push(signal);
36+
}
37+
}
38+
39+
test('should click', async ({ context }) => {
40+
const browserContext = context as BrowserContext;
41+
const log = new RecorderLog();
42+
await browserContext._enableRecorder({
43+
mode: 'recording',
44+
recorderMode: 'api',
45+
}, log);
46+
47+
const page = await browserContext.newPage();
48+
await page.setContent(`<button onclick="console.log('click')">Submit</button>`);
49+
await page.getByRole('button', { name: 'Submit' }).click();
50+
51+
expect(log.actions).toEqual([
52+
expect.objectContaining({
53+
action: { name: 'openPage', url: 'about:blank', signals: [] },
54+
startTime: expect.any(Number)
55+
}),
56+
expect.objectContaining({
57+
action: expect.objectContaining({
58+
name: 'click',
59+
selector: 'internal:role=button[name="Submit"i]',
60+
ref: 'e2',
61+
ariaSnapshot: '- button "Submit" [active] [ref=e2] [cursor=pointer]',
62+
}),
63+
startTime: expect.any(Number),
64+
}),
65+
]);
66+
});
67+
68+
test('should type', async ({ context }) => {
69+
const browserContext = context as BrowserContext;
70+
const log = new RecorderLog();
71+
await browserContext._enableRecorder({
72+
mode: 'recording',
73+
recorderMode: 'api',
74+
}, log);
75+
76+
const page = await browserContext.newPage();
77+
await page.setContent(`<input type="text" />`);
78+
await page.getByRole('textbox').pressSequentially('Hello');
79+
80+
expect(log.actions[log.actions.length - 1]).toEqual(
81+
expect.objectContaining({
82+
action: expect.objectContaining({
83+
name: 'fill',
84+
selector: 'internal:role=textbox',
85+
ref: 'e2',
86+
ariaSnapshot: '- textbox [active] [ref=e2] [cursor=pointer]: Hello',
87+
}),
88+
startTime: expect.any(Number),
89+
}));
90+
});

0 commit comments

Comments
 (0)