diff --git a/examples/recorder-crx/options.html b/examples/recorder-crx/options.html new file mode 100644 index 000000000..f4a922000 --- /dev/null +++ b/examples/recorder-crx/options.html @@ -0,0 +1,16 @@ + + + + + + Playwright CRX - Configuration + + +
+ + + +
+ + + diff --git a/examples/recorder-crx/public/manifest.json b/examples/recorder-crx/public/manifest.json index fe698f7a5..4d2aafb08 100644 --- a/examples/recorder-crx/public/manifest.json +++ b/examples/recorder-crx/public/manifest.json @@ -19,5 +19,9 @@ }, "default_title": "Record" }, - "permissions": ["debugger", "tabs", "contextMenus"] + "options_ui": { + "page": "options.html", + "open_in_tab": false + }, + "permissions": ["debugger", "tabs", "contextMenus", "storage"] } diff --git a/examples/recorder-crx/src/background.ts b/examples/recorder-crx/src/background.ts index 05b37e4d2..392bbeebb 100644 --- a/examples/recorder-crx/src/background.ts +++ b/examples/recorder-crx/src/background.ts @@ -15,7 +15,7 @@ */ import type { CrxApplication } from 'playwright-crx'; -import { crx, _debug, _setUnderTest } from 'playwright-crx'; +import playwright, { crx, _debug, _setUnderTest } from 'playwright-crx'; type Mode = 'none' | 'recording' | 'inspecting' | 'assertingText' | 'recording-inspecting' | 'standby' | 'assertingVisibility' | 'assertingValue'; @@ -106,6 +106,10 @@ async function attach(tab: chrome.tabs.Tab) { } } +async function setTestIdAttributeName(testIdAttributeName: string) { + playwright.selectors.setTestIdAttribute(testIdAttributeName); +} + chrome.action.onClicked.addListener(attach); chrome.contextMenus.create({ @@ -118,5 +122,11 @@ chrome.contextMenus.onClicked.addListener(async (_, tab) => { if (tab) await attach(tab); }); +chrome.storage.sync.onChanged.addListener(async ({ testIdAttributeName }) => { + if (!testIdAttributeName) return; + + await setTestIdAttributeName(testIdAttributeName.newValue); +}); + // for testing -Object.assign(self, { attach, _debug, _setUnderTest }); +Object.assign(self, { attach, setTestIdAttributeName, _debug, _setUnderTest }); diff --git a/examples/recorder-crx/src/options.css b/examples/recorder-crx/src/options.css new file mode 100644 index 000000000..10ad21ef5 --- /dev/null +++ b/examples/recorder-crx/src/options.css @@ -0,0 +1,51 @@ +body { + font-family: sans-serif; + margin: 20px; +} + +h1 { + text-align: center; + margin-bottom: 15px; +} + +form { + display: flex; + flex-direction: column; + gap: 10px; +} + +label { + font-weight: bold; +} + +input[type="text"] { + padding: 5px; + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; +} + +button[type="submit"] { + background-color: #4CAF50; + border: none; + color: white; + padding: 10px 20px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.2s ease-in-out; +} + +button:hover { + background-color: #45A049; +} + +button[type="submit"]:disabled { + background-color: #ccc; + color: #666; + cursor: not-allowed; +} diff --git a/examples/recorder-crx/src/options.ts b/examples/recorder-crx/src/options.ts new file mode 100644 index 000000000..3ebe7ceb7 --- /dev/null +++ b/examples/recorder-crx/src/options.ts @@ -0,0 +1,31 @@ +import './options.css'; + +async function initialize() { + const formElem = document.getElementById('options-form') as HTMLFormElement; + const testIdAttributeNameElem = document.getElementById('test-id') as HTMLInputElement; + const submitElem = document.getElementById('submit') as HTMLButtonElement; + + if (!testIdAttributeNameElem || !formElem || !submitElem) return; + + testIdAttributeNameElem.addEventListener('input', () => { + submitElem.disabled = false; + }); + + formElem.addEventListener('submit', (e) => { + if (!formElem.reportValidity()) return; + + e.preventDefault(); + + submitElem.disabled = true; + const testIdAttributeName = testIdAttributeNameElem.value; + chrome.storage.sync.set({ testIdAttributeName }).catch(() => {}); + + return false; + }); + + submitElem.disabled = true; + const { testIdAttributeName } = await chrome.storage.sync.get('testIdAttributeName'); + testIdAttributeNameElem.value = testIdAttributeName ?? 'data-testid'; +} + +initialize(); diff --git a/examples/recorder-crx/vite.config.ts b/examples/recorder-crx/vite.config.ts index 0770a3d73..407a5092f 100644 --- a/examples/recorder-crx/vite.config.ts +++ b/examples/recorder-crx/vite.config.ts @@ -33,6 +33,7 @@ export default defineConfig({ plugins: [sourcemaps()], input: { 'background': path.resolve(__dirname, 'src/background.ts'), + 'options': path.resolve(__dirname, 'options.html'), }, output: { entryFileNames: '[name].js', diff --git a/tests/crx/recorder.spec.ts b/tests/crx/recorder.spec.ts index e77f4395d..5336c856f 100644 --- a/tests/crx/recorder.spec.ts +++ b/tests/crx/recorder.spec.ts @@ -204,7 +204,7 @@ test('should record with all supported actions and assertions', async ({ context }); const context = await browser.newContext(); const page = await context.newPage(); - await page.goto('http://127.0.0.1:3000/root.html'); + await page.goto('${baseURL}/root.html'); await page.getByRole('checkbox').check(); await page.getByRole('button', { name: 'button' }).click(); await page.getByRole('checkbox').uncheck(); @@ -227,3 +227,40 @@ test('should record with all supported actions and assertions', async ({ context await expect(recorderPage.locator('.CodeMirror-line')).toHaveText(code.split('\n')); }); + +test('should record with custom testid', async ({ page, attachRecorder, recordAction, baseURL, extensionServiceWorker }) => { + const recorderPage = await attachRecorder(page); + await recordAction(() => page.goto(`${baseURL}/empty.html`)); + await page.setContent(` + + + `); + await recordAction(() => page.locator('button').first().click()); + await extensionServiceWorker.evaluate(async () => { + await (globalThis as any).setTestIdAttributeName('data-foobar'); + }); + // injected recorder poll period + await page.waitForTimeout(1000); + await recordAction(() => page.locator('button').nth(1).click()); + + await recorderPage.getByTitle('Record').click(); + + const code = `const { chromium } = require('playwright'); + +(async () => { + const browser = await chromium.launch({ + headless: false + }); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto('${baseURL}/empty.html'); + await page.getByTestId('btn-testid').click(); + await page.getByTestId('btn-foobar').click(); + + // --------------------- + await context.close(); + await browser.close(); +})();`; + + await expect(recorderPage.locator('.CodeMirror-line')).toHaveText(code.split('\n')); +});