From 076a91e30eb175fb2a6db17abe360a7cbd89a975 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 25 Nov 2024 09:58:59 +0100 Subject: [PATCH 01/68] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20use=20playwright=20f?= =?UTF-8?q?or=20e2e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++ package.json | 2 + playwright.config.ts | 80 +++++++++++++++++++++++ test/e2e/lib/framework/createTest.ts | 73 +++++++++++++++------ test/e2e/lib/framework/flushEvents.ts | 7 +- test/e2e/lib/framework/logger.ts | 6 +- test/e2e/lib/framework/pageSetups.ts | 8 +-- test/e2e/lib/framework/waitForRequests.ts | 14 ++-- test/e2e/lib/helpers/browser.ts | 41 +++++------- test/e2e/scenario/rum/init.scenario.ts | 50 +++++++------- yarn.lock | 41 +++++++++++- 11 files changed, 242 insertions(+), 84 deletions(-) create mode 100644 playwright.config.ts diff --git a/.gitignore b/.gitignore index d6b930e824..0d96d9e327 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ browserstack.err !.yarn/releases !.yarn/sdks !.yarn/versions +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index 85b90e0a34..c25c082ff1 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,13 @@ }, "devDependencies": { "@jsdevtools/coverage-istanbul-loader": "3.0.5", + "@playwright/test": "1.49.0", "@types/chrome": "0.0.287", "@types/connect-busboy": "1.0.3", "@types/cors": "2.8.17", "@types/express": "4.17.21", "@types/jasmine": "3.10.18", + "@types/node": "22.10.2", "@typescript-eslint/eslint-plugin": "8.16.0", "@typescript-eslint/parser": "8.16.0", "@wdio/browserstack-service": "8.40.6", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..915ed9c982 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './test/e2e/scenario', + testMatch: '**/init.scenario.ts', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 91ec8eb2ea..8fe33ea0e2 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,8 +1,11 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { DefaultPrivacyLevel } from '@datadog/browser-rum' +import type { BrowserContext, Page } from '@playwright/test' +import { test, expect } from '@playwright/test' import { getRunId } from '../../../envUtils' -import { deleteAllCookies, getBrowserName, withBrowserLogs } from '../helpers/browser' +import type { BrowserLog } from '../helpers/browser' +import { BrowserLogsManager, deleteAllCookies } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import { IntakeRegistry } from './intakeRegistry' @@ -43,9 +46,15 @@ interface TestContext { crossOriginUrl: string intakeRegistry: IntakeRegistry servers: Servers + page: Page + browserContext: BrowserContext + withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => void + flushBrowserLogs: () => void + flushEvents: () => Promise + deleteAllCookies: () => Promise } -type TestRunner = (testContext: TestContext) => Promise +type TestRunner = (testContext: TestContext) => Promise | void class TestBuilder { private rumConfiguration: RumInitConfiguration | undefined = undefined @@ -132,7 +141,7 @@ class TestBuilder { } if (this.alsoRunWithRumSlim) { - describe(this.title, () => { + test.describe(this.title, () => { declareTestsForSetups('rum', setups, setupOptions, runner) declareTestsForSetups( 'rum-slim', @@ -155,11 +164,6 @@ class TestBuilder { } } -interface ItResult { - getFullName(): string -} -declare function it(expectation: string, assertion?: jasmine.ImplementationCallback, timeout?: number): ItResult - function declareTestsForSetups( title: string, setups: Array<{ factory: SetupFactory; name?: string }>, @@ -178,53 +182,82 @@ function declareTestsForSetups( } function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { - const spec = it(title, async () => { - log(`Start '${spec.getFullName()}' in ${getBrowserName()}`) - setupOptions.context.test_name = spec.getFullName() + test(title, async ({ page, context, browserName }) => { + const title = test.info().titlePath.join(' > ') + log(`Start '${title}' in ${browserName}`) + + setupOptions.context.test_name = title const servers = await getTestServers() + const browserLogs = new BrowserLogsManager() - const testContext = createTestContext(servers, setupOptions) + const testContext = createTestContext(servers, page, context, browserLogs, setupOptions) servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry)) const setup = factory(setupOptions, servers) servers.base.bindServerApp(createMockServerApp(servers, setup)) servers.crossOrigin.bindServerApp(createMockServerApp(servers, setup)) - await setUpTest(testContext) + await setUpTest(browserLogs, testContext) try { await runner(testContext) } finally { await tearDownTest(testContext) - log(`End '${spec.getFullName()}'`) + log(`End '${title}'`) } }) } -function createTestContext(servers: Servers, { basePath }: SetupOptions): TestContext { +function createTestContext( + servers: Servers, + page: Page, + browserContext: BrowserContext, + browserLogsManager: BrowserLogsManager, + { basePath }: SetupOptions +): TestContext { return { baseUrl: servers.base.url + basePath, crossOriginUrl: servers.crossOrigin.url, intakeRegistry: new IntakeRegistry(), servers, + page, + browserContext, + withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => { + cb(browserLogsManager.get()) + browserLogsManager.clear() + }, + flushBrowserLogs: () => { + browserLogsManager.clear() + }, + flushEvents: () => flushEvents(page), + deleteAllCookies: () => deleteAllCookies(browserContext), } } -async function setUpTest({ baseUrl }: TestContext) { - await browser.url(baseUrl) +async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page, browserContext }: TestContext) { + browserContext.on('console', (msg) => + browserLogsManager.add({ + level: msg.type() as BrowserLog['level'], + message: msg.text(), + source: 'console', + timestamp: Date.now(), + }) + ) + + await page.goto(baseUrl) await waitForServersIdle() } -async function tearDownTest({ intakeRegistry }: TestContext) { +async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, deleteAllCookies }: TestContext) { await flushEvents() expect(intakeRegistry.telemetryErrorEvents).toEqual([]) validateRumFormat(intakeRegistry.rumEvents) - await withBrowserLogs((logs) => { + withBrowserLogs((logs) => { logs.forEach((browserLog) => { log(`Browser ${browserLog.source}: ${browserLog.level} ${browserLog.message}`) }) - expect(logs.filter((l) => (l as any).level === 'SEVERE')).toEqual([]) + expect(logs.filter((log) => log.level === 'error')).toEqual([]) }) await deleteAllCookies() } diff --git a/test/e2e/lib/framework/flushEvents.ts b/test/e2e/lib/framework/flushEvents.ts index f2181c6cb3..39c6d39874 100644 --- a/test/e2e/lib/framework/flushEvents.ts +++ b/test/e2e/lib/framework/flushEvents.ts @@ -1,8 +1,9 @@ +import type { Page } from '@playwright/test' import { getTestServers, waitForServersIdle } from './httpServers' import { waitForRequests } from './waitForRequests' -export async function flushEvents() { - await waitForRequests() +export async function flushEvents(page: Page) { + await waitForRequests(page) const servers = await getTestServers() @@ -20,6 +21,6 @@ export async function flushEvents() { // The issue mainly occurs with local e2e tests (not browserstack), because the network latency is // very low (same machine), so the request resolves very quickly. In real life conditions, this // issue is mitigated, because requests will likely take a few milliseconds to reach the server. - await browser.url(`${servers.base.url}/ok?duration=200`) + await page.goto(`${servers.base.url}/ok?duration=200`) await waitForServersIdle() } diff --git a/test/e2e/lib/framework/logger.ts b/test/e2e/lib/framework/logger.ts index 91880f9de2..f2b007bc78 100644 --- a/test/e2e/lib/framework/logger.ts +++ b/test/e2e/lib/framework/logger.ts @@ -1,8 +1,10 @@ import * as fs from 'fs' import { inspect } from 'util' -const logsPath = (browser.options as WebdriverIO.Config & { logsPath: string }).logsPath -const stream: { write(s: string): void } = logsPath ? fs.createWriteStream(logsPath, { flags: 'a' }) : process.stdout +const logsPath = undefined // (browser.options as WebdriverIO.Config & { logsPath: string }).logsPath +const stream: { write(s: string): void } = logsPath + ? fs.createWriteStream(logsPath, { flags: 'a' }) + : { write: () => {} } // TODO: why do we need to log to stdout? export function log(...args: any[]) { const prefix = `[${process.pid}] ${new Date().toISOString()}` diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index 0645200b44..f3f34e536b 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -21,10 +21,10 @@ export interface SetupOptions { export type SetupFactory = (options: SetupOptions, servers: Servers) => string -const isBrowserStack = - 'services' in browser.options && - browser.options.services && - browser.options.services.some((service) => (Array.isArray(service) ? service[0] : service) === 'browserstack') +const isBrowserStack = false +// 'services' in browser.options && +// browser.options.services && +// browser.options.services.some((service) => (Array.isArray(service) ? service[0] : service) === 'browserstack') const isContinuousIntegration = Boolean(process.env.CI_JOB_ID) diff --git a/test/e2e/lib/framework/waitForRequests.ts b/test/e2e/lib/framework/waitForRequests.ts index ffe560d8ad..03b302b3f6 100644 --- a/test/e2e/lib/framework/waitForRequests.ts +++ b/test/e2e/lib/framework/waitForRequests.ts @@ -1,3 +1,4 @@ +import type { Page } from '@playwright/test' import { waitForServersIdle } from './httpServers' /** @@ -10,11 +11,14 @@ import { waitForServersIdle } from './httpServers' * As a workaround, this function delays the `waitForServersIdle()` call by doing a browser * roundtrip, ensuring requests have plenty of time to reach the local server. */ -export async function waitForRequests() { - await browser.executeAsync((done) => - setTimeout(() => { - done(undefined) - }, 200) +export async function waitForRequests(page: Page) { + await page.evaluate( + () => + new Promise((resolve) => { + setTimeout(() => { + resolve(undefined) + }, 200) + }) ) await waitForServersIdle() } diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index f81ad40186..b411b5a4e6 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -1,4 +1,5 @@ import * as os from 'os' +import type { BrowserContext } from '@playwright/test' // To keep tests sane, ensure we got a fixed list of possible platforms and browser names. const validPlatformNames = ['windows', 'macos', 'linux', 'ios', 'android'] as const @@ -56,39 +57,33 @@ function includes(list: readonly T[], item: unknown): item is T { return list.includes(item as any) } -interface BrowserLog { - level: string +export interface BrowserLog { + level: 'log' | 'debug' | 'info' | 'error' | 'warning' message: string source: string timestamp: number } -export async function withBrowserLogs(fn: (logs: BrowserLog[]) => void) { - // browser.getLogs is not defined when using a remote webdriver service. We should find an - // alternative at some point. - // https://github.com/webdriverio/webdriverio/issues/4275 - if (browser.getLogs) { - const logs = (await browser.getLogs('browser')) as BrowserLog[] - fn(logs) +export class BrowserLogsManager { + private logs: BrowserLog[] = [] + + add(log: BrowserLog) { + this.logs.push(log) } -} -export async function flushBrowserLogs() { - await withBrowserLogs(() => { - // Ignore logs - }) + get() { + return this.logs + } + + clear() { + this.logs = [] + } } +// TODO, see if we can use the browser context to clear cookies or we should keep the previous hack // wdio method does not work for some browsers -export function deleteAllCookies() { - return browser.execute(() => { - const cookies = document.cookie.split(';') - for (const cookie of cookies) { - const eqPos = cookie.indexOf('=') - const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie - document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;samesite=strict` - } - }) +export function deleteAllCookies(context: BrowserContext) { + return context.clearCookies() } export function setCookie(name: string, value: string, expiresDelay: number = 0) { diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 2b72007984..341883c041 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -1,19 +1,19 @@ import type { Context } from '@datadog/browser-core' +import { test, expect } from '@playwright/test' import type { IntakeRegistry } from '../../lib/framework' -import { flushEvents, createTest } from '../../lib/framework' -import { withBrowserLogs } from '../../lib/helpers/browser' +import { createTest } from '../../lib/framework' -describe('API calls and events around init', () => { +test.describe('API calls and events around init', () => { createTest('should display a console log when calling init without configuration') .withRum() .withRumInit(() => { ;(window.DD_RUM! as unknown as { init(): void }).init() }) - .run(async () => { - await withBrowserLogs((logs) => { + .run(({ withBrowserLogs }) => { + withBrowserLogs((logs) => { expect(logs.length).toBe(1) - expect(logs[0].message).toEqual(jasmine.stringContaining('Datadog Browser SDK')) - expect(logs[0].message).toEqual(jasmine.stringContaining('Missing configuration')) + expect(logs[0].message).toEqual(expect.stringContaining('Datadog Browser SDK')) + expect(logs[0].message).toEqual(expect.stringContaining('Missing configuration')) }) }) @@ -35,19 +35,19 @@ describe('API calls and events around init', () => { setTimeout(() => window.DD_RUM!.init(configuration), 30) }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const initialView = intakeRegistry.rumViewEvents[0] expect(initialView.view.name).toBeUndefined() expect(initialView.view.custom_timings).toEqual({ - before_manual_view: jasmine.any(Number), + before_manual_view: expect.any(Number), }) const manualView = intakeRegistry.rumViewEvents[1] expect(manualView.view.name).toBe('manual view') expect(manualView.view.custom_timings).toEqual({ - after_manual_view: jasmine.any(Number), + after_manual_view: expect.any(Number), }) const documentEvent = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'document')! @@ -92,15 +92,15 @@ describe('API calls and events around init', () => { window.DD_RUM!.setViewName('after manual view') }, 40) }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const initialView = intakeRegistry.rumViewEvents[0] expect(initialView.view.name).toBe('after manual view') expect(initialView.view.custom_timings).toEqual({ - before_init: jasmine.any(Number), - before_manual_view: jasmine.any(Number), - after_manual_view: jasmine.any(Number), + before_init: expect.any(Number), + before_manual_view: expect.any(Number), + after_manual_view: expect.any(Number), }) const documentEvent = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'document')! @@ -142,13 +142,13 @@ describe('API calls and events around init', () => { window.DD_RUM!.addError('after manual view') }, 20) }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const initialView = intakeRegistry.rumViewEvents[0] const nextView = intakeRegistry.rumViewEvents[1] - expect(initialView.context).toEqual(jasmine.objectContaining({ foo: 'bar', bar: 'foo' })) + expect(initialView.context).toEqual(expect.objectContaining({ foo: 'bar', bar: 'foo' })) expect(nextView.context!.foo).toBeUndefined() expectToHaveActions( @@ -178,7 +178,7 @@ describe('API calls and events around init', () => { }) }) -describe('beforeSend', () => { +test.describe('beforeSend', () => { createTest('allows to edit events context with feature flag') .withRum({ beforeSend: (event: any) => { @@ -187,13 +187,13 @@ describe('beforeSend', () => { }, }) .withRumSlim() - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const initialView = intakeRegistry.rumViewEvents[0] - expect(initialView.context).toEqual(jasmine.objectContaining({ foo: 'bar' })) + expect(initialView.context).toEqual(expect.objectContaining({ foo: 'bar' })) const initialDocument = intakeRegistry.rumResourceEvents[0] - expect(initialDocument.context).toEqual(jasmine.objectContaining({ foo: 'bar' })) + expect(initialDocument.context).toEqual(expect.objectContaining({ foo: 'bar' })) }) createTest('allows to replace events context') @@ -209,13 +209,13 @@ describe('beforeSend', () => { window.DD_RUM!.setGlobalContextProperty('foo', 'baz') window.DD_RUM!.setGlobalContextProperty('zig', 'zag') }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const initialView = intakeRegistry.rumViewEvents[0] - expect(initialView.context).toEqual(jasmine.objectContaining({ foo: 'bar' })) + expect(initialView.context).toEqual(expect.objectContaining({ foo: 'bar' })) const initialDocument = intakeRegistry.rumResourceEvents[0] - expect(initialDocument.context).toEqual(jasmine.objectContaining({ foo: 'bar' })) + expect(initialDocument.context).toEqual(expect.objectContaining({ foo: 'bar' })) }) }) @@ -230,7 +230,7 @@ function expectToHaveErrors( expect(registryError.error.message).toBe(expectedError.message) expect(registryError.view.id).toBe(expectedError.viewId) if (expectedError.context) { - expect(registryError.context).toEqual(jasmine.objectContaining(expectedError.context)) + expect(registryError.context).toEqual(expect.objectContaining(expectedError.context)) } } } @@ -249,7 +249,7 @@ function expectToHaveActions( expect(registryAction.view.name).toBe(expectedAction.viewName) } if (expectedAction.context) { - expect(registryAction.context).toEqual(jasmine.objectContaining(expectedAction.context)) + expect(registryAction.context).toEqual(expect.objectContaining(expectedAction.context)) } } } diff --git a/yarn.lock b/yarn.lock index e9a29023af..bd8065c6aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1423,6 +1423,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:1.49.0": + version: 1.49.0 + resolution: "@playwright/test@npm:1.49.0" + dependencies: + playwright: "npm:1.49.0" + bin: + playwright: cli.js + checksum: 10c0/2890d52ee45bd83b5501f17a77c77f12ba934d257fda4b288405c6d91f94b83c4fcbdff3c0be89c2aaeea3d13576b72ec9a70be667ff844b342044afd72a246e + languageName: node + linkType: hard + "@puppeteer/browsers@npm:1.9.1, @puppeteer/browsers@npm:^1.6.0": version: 1.9.1 resolution: "@puppeteer/browsers@npm:1.9.1" @@ -3438,11 +3449,13 @@ __metadata: resolution: "browser-sdk@workspace:." dependencies: "@jsdevtools/coverage-istanbul-loader": "npm:3.0.5" + "@playwright/test": "npm:1.49.0" "@types/chrome": "npm:0.0.287" "@types/connect-busboy": "npm:1.0.3" "@types/cors": "npm:2.8.17" "@types/express": "npm:4.17.21" "@types/jasmine": "npm:3.10.18" + "@types/node": "npm:22.10.2" "@typescript-eslint/eslint-plugin": "npm:8.16.0" "@typescript-eslint/parser": "npm:8.16.0" "@wdio/browserstack-service": "npm:8.40.6" @@ -6430,7 +6443,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -6440,7 +6453,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -10803,6 +10816,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.49.0": + version: 1.49.0 + resolution: "playwright-core@npm:1.49.0" + bin: + playwright-core: cli.js + checksum: 10c0/22c1a72fabdcc87bd1cd4d40a032d2c5b94cf94ba7484dc182048c3fa1c8ec26180b559d8cac4ca9870e8fd6bdf5ef9d9f54e7a31fd60d67d098fcffc5e4253b + languageName: node + linkType: hard + +"playwright@npm:1.49.0": + version: 1.49.0 + resolution: "playwright@npm:1.49.0" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.49.0" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10c0/e94d662747cd147d0573570fec90dadc013c1097595714036fc8934a075c5a82ab04a49111b03b1f762ea86429bdb7c94460901896901e20970b30ce817cc93f + languageName: node + linkType: hard + "pluralize@npm:^8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0" From 8dfc67fc4969fa82380980e10489b3fa14fc098d Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 23 Dec 2024 13:00:15 +0100 Subject: [PATCH 02/68] =?UTF-8?q?=E2=9C=85=20add=20logs=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 2 +- test/e2e/scenario/logs.scenario.ts | 124 +++++++++++++++-------------- 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 915ed9c982..1be176bb49 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,7 +13,7 @@ import { defineConfig, devices } from '@playwright/test' */ export default defineConfig({ testDir: './test/e2e/scenario', - testMatch: '**/init.scenario.ts', + testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts'], /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index ffbd1b0c54..6e475aa987 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -1,15 +1,15 @@ import { DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT } from '@datadog/browser-logs/cjs/domain/configuration' -import { createTest, flushEvents } from '../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest } from '../lib/framework' import { APPLICATION_ID } from '../lib/helpers/configuration' -import { flushBrowserLogs, withBrowserLogs } from '../lib/helpers/browser' const UNREACHABLE_URL = 'http://localhost:9999/unreachable' -describe('logs', () => { +test.describe('logs', () => { createTest('send logs') .withLogs() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { window.DD_LOGS!.logger.log('hello') }) await flushEvents() @@ -19,52 +19,56 @@ describe('logs', () => { createTest('display logs in the console') .withLogs() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page, withBrowserLogs }) => { + await page.evaluate(() => { window.DD_LOGS!.logger.setHandler('console') window.DD_LOGS!.logger.warn('hello') }) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(0) - await withBrowserLogs((logs) => { + withBrowserLogs((logs) => { expect(logs.length).toBe(1) - expect(logs[0].level).toBe('WARNING') - expect(logs[0].message).not.toEqual(jasmine.stringContaining('Datadog Browser SDK')) - expect(logs[0].message).toEqual(jasmine.stringContaining('hello')) + expect(logs[0].level).toBe('warning') + expect(logs[0].message).not.toEqual(expect.stringContaining('Datadog Browser SDK')) + expect(logs[0].message).toEqual(expect.stringContaining('hello')) }) }) createTest('send console errors') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page, withBrowserLogs }) => { + await page.evaluate(() => { console.error('oh snap') }) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) expect(intakeRegistry.logsEvents[0].message).toBe('oh snap') - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) createTest('send XHR network errors') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((unreachableUrl, done) => { - const xhr = new XMLHttpRequest() - xhr.addEventListener('error', () => done(undefined)) - xhr.open('GET', unreachableUrl) - xhr.send() - }, UNREACHABLE_URL) + .run(async ({ intakeRegistry, flushEvents, withBrowserLogs, page }) => { + await page.evaluate( + (unreachableUrl) => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + xhr.addEventListener('error', () => resolve()) + xhr.open('GET', unreachableUrl) + xhr.send() + }), + UNREACHABLE_URL + ) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) expect(intakeRegistry.logsEvents[0].message).toBe(`XHR error GET ${UNREACHABLE_URL}`) expect(intakeRegistry.logsEvents[0].origin).toBe('network') - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { // Some browser report two errors: // * failed to load resource // * blocked by CORS policy @@ -74,19 +78,15 @@ describe('logs', () => { createTest('send fetch network errors') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((unreachableUrl, done) => { - fetch(unreachableUrl).catch(() => { - done(undefined) - }) - }, UNREACHABLE_URL) + .run(async ({ intakeRegistry, flushEvents, page, withBrowserLogs }) => { + await page.evaluate((unreachableUrl) => fetch(unreachableUrl).catch(() => {}), UNREACHABLE_URL) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) expect(intakeRegistry.logsEvents[0].message).toBe(`Fetch error GET ${UNREACHABLE_URL}`) expect(intakeRegistry.logsEvents[0].origin).toBe('network') - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { // Some browser report two errors: // * failed to load resource // * blocked by CORS policy @@ -96,10 +96,8 @@ describe('logs', () => { createTest('keep only the first bytes of the response') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry, baseUrl, servers }) => { - await browser.executeAsync((done) => { - fetch('/throw-large-response').then(() => done(undefined), console.log) - }) + .run(async ({ intakeRegistry, baseUrl, servers, flushEvents, page, withBrowserLogs }) => { + await page.evaluate(() => fetch('/throw-large-response').then(() => {}, console.log)) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) @@ -115,7 +113,7 @@ describe('logs', () => { DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT ) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { // Some browser report two errors: // * the server responded with a status of 500 // * canceling the body stream is reported as a network error (net::ERR_FAILED) @@ -125,29 +123,33 @@ describe('logs', () => { createTest('track fetch error') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry, baseUrl }) => { - await browser.executeAsync((unreachableUrl, done) => { - let count = 0 - fetch('/throw') - .then(() => (count += 1)) - .catch((err) => console.error(err)) - fetch('/unknown') - .then(() => (count += 1)) - .catch((err) => console.error(err)) - fetch(unreachableUrl).catch(() => (count += 1)) - fetch('/ok') - .then(() => (count += 1)) - .catch((err) => console.error(err)) - - const interval = setInterval(() => { - if (count === 4) { - clearInterval(interval) - done(undefined) - } - }, 500) - }, UNREACHABLE_URL) - - await flushBrowserLogs() + .run(async ({ intakeRegistry, baseUrl, flushEvents, flushBrowserLogs, page }) => { + await page.evaluate( + (unreachableUrl) => + new Promise((resolve) => { + let count = 0 + fetch('/throw') + .then(() => (count += 1)) + .catch((err) => console.error(err)) + fetch('/unknown') + .then(() => (count += 1)) + .catch((err) => console.error(err)) + fetch(unreachableUrl).catch(() => (count += 1)) + fetch('/ok') + .then(() => (count += 1)) + .catch((err) => console.error(err)) + + const interval = setInterval(() => { + if (count === 4) { + clearInterval(interval) + resolve() + } + }, 500) + }), + UNREACHABLE_URL + ) + + flushBrowserLogs() await flushEvents() expect(intakeRegistry.logsEvents.length).toEqual(2) @@ -167,8 +169,8 @@ describe('logs', () => { createTest('add RUM internal context to logs') .withRum() .withLogs() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { window.DD_LOGS!.logger.log('hello') }) await flushEvents() @@ -184,8 +186,8 @@ describe('logs', () => { return true }, }) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { window.DD_LOGS!.logger.log('hello', {}) }) await flushEvents() From be517d889f03dd8d5107493057e3600ada46d252 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 24 Dec 2024 09:27:19 +0100 Subject: [PATCH 03/68] =?UTF-8?q?=E2=9C=85=20add=20actions=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 18 +-- test/e2e/lib/framework/createTest.ts | 18 ++- test/e2e/scenario/rum/actions.scenario.ts | 174 +++++++++++----------- 3 files changed, 109 insertions(+), 101 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 1be176bb49..8f7417228d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,7 +13,7 @@ import { defineConfig, devices } from '@playwright/test' */ export default defineConfig({ testDir: './test/e2e/scenario', - testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts'], + testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts', '**/actions.scenario.ts'], /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -40,15 +40,15 @@ export default defineConfig({ use: { ...devices['Desktop Chrome'] }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, /* Test against mobile viewports. */ // { diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 8fe33ea0e2..d6755051ec 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -48,6 +48,7 @@ interface TestContext { servers: Servers page: Page browserContext: BrowserContext + browserName: 'chromium' | 'firefox' | 'webkit' withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => void flushBrowserLogs: () => void flushEvents: () => Promise @@ -191,7 +192,7 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa const servers = await getTestServers() const browserLogs = new BrowserLogsManager() - const testContext = createTestContext(servers, page, context, browserLogs, setupOptions) + const testContext = createTestContext(servers, page, context, browserLogs, browserName, setupOptions) servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry)) const setup = factory(setupOptions, servers) @@ -214,6 +215,7 @@ function createTestContext( page: Page, browserContext: BrowserContext, browserLogsManager: BrowserLogsManager, + browserName: TestContext['browserName'], { basePath }: SetupOptions ): TestContext { return { @@ -223,6 +225,7 @@ function createTestContext( servers, page, browserContext, + browserName, withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => { cb(browserLogsManager.get()) browserLogsManager.clear() @@ -236,14 +239,23 @@ function createTestContext( } async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page, browserContext }: TestContext) { - browserContext.on('console', (msg) => + browserContext.on('console', (msg) => { browserLogsManager.add({ level: msg.type() as BrowserLog['level'], message: msg.text(), source: 'console', timestamp: Date.now(), }) - ) + }) + + browserContext.on('weberror', (webError) => { + browserLogsManager.add({ + level: 'error', + message: webError.error().message, + source: 'console', + timestamp: Date.now(), + }) + }) await page.goto(baseUrl) await waitForServersIdle() diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index fdaf9b7109..1abc6af8e6 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -1,7 +1,7 @@ -import { getBrowserName, withBrowserLogs } from '../../lib/helpers/browser' -import { createTest, flushEvents, html, waitForServersIdle } from '../../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest, html, waitForServersIdle } from '../../lib/framework' -describe('action collection', () => { +test.describe('action collection', () => { createTest('track a click action') .withRum({ trackUserInteractions: true, enableExperimentalFeatures: ['action_name_masking'] }) .withBody(html` @@ -13,23 +13,23 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents.length).toBe(1) expect(actionEvents[0]).toEqual( - jasmine.objectContaining({ + expect.objectContaining({ action: { error: { count: 0, }, - id: jasmine.any(String), - loading_time: jasmine.any(Number), + id: expect.any(String), + loading_time: expect.any(Number), long_task: { - count: jasmine.any(Number), + count: expect.any(Number), }, resource: { count: 0, @@ -42,17 +42,17 @@ describe('action collection', () => { type: [], }, }, - _dd: jasmine.objectContaining({ + _dd: expect.objectContaining({ action: { - target: jasmine.objectContaining({ - selector: jasmine.any(String), - width: jasmine.any(Number), - height: jasmine.any(Number), + target: expect.objectContaining({ + selector: expect.any(String), + width: expect.any(Number), + height: expect.any(Number), }), name_source: 'text_content', position: { - x: jasmine.any(Number), - y: jasmine.any(Number), + x: expect.any(Number), + y: expect.any(Number), }, }, }), @@ -73,8 +73,8 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -84,32 +84,33 @@ describe('action collection', () => { expect(actionEvents[0]._dd.action?.target?.selector).toBe('BODY>BUTTON') }) - // When the target element changes between mousedown and mouseup, Firefox does not dispatch a - // click event. Skip this test. - if (getBrowserName() !== 'firefox') { - createTest('does not report a click on the body when the target element changes between mousedown and mouseup') - .withRum({ trackUserInteractions: true }) - .withBody(html` - - - `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') - await button.click() - await flushEvents() - const actionEvents = intakeRegistry.rumActionEvents - - expect(actionEvents.length).toBe(1) - expect(actionEvents[0].action?.target?.name).toBe('click me') - }) - } + createTest('does not report a click on the body when the target element changes between mousedown and mouseup') + .withRum({ trackUserInteractions: true }) + .withBody(html` + + + `) + .run(async ({ intakeRegistry, flushEvents, browserName, page }) => { + test.skip( + browserName === 'firefox', + 'When the target element changes between mousedown and mouseup, Firefox does not dispatch a click event.' + ) + + const button = page.locator('button') + await button.click() + await flushEvents() + const actionEvents = intakeRegistry.rumActionEvents + + expect(actionEvents.length).toBe(1) + expect(actionEvents[0].action?.target?.name).toBe('click me') + }) createTest('associate a request to its action') .withRum({ trackUserInteractions: true }) @@ -122,8 +123,8 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await waitForServersIdle() await flushEvents() @@ -135,10 +136,10 @@ describe('action collection', () => { error: { count: 0, }, - id: jasmine.any(String) as unknown as string, - loading_time: jasmine.any(Number) as unknown as number, + id: expect.any(String) as unknown as string, + loading_time: expect.any(Number) as unknown as number, long_task: { - count: jasmine.any(Number) as unknown as number, + count: expect.any(Number) as unknown as number, }, resource: { count: 1, @@ -169,8 +170,8 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -195,8 +196,8 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, withBrowserLogs, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -207,7 +208,7 @@ describe('action collection', () => { expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(1) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) @@ -215,8 +216,8 @@ describe('action collection', () => { createTest('collect a "dead click"') .withRum({ trackUserInteractions: true }) .withBody(html` `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -230,8 +231,8 @@ describe('action collection', () => { createTest('do not consider a click on a checkbox as "dead_click"') .withRum({ trackUserInteractions: true }) .withBody(html` `) - .run(async ({ intakeRegistry }) => { - const input = await $('input') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const input = page.locator('input') await input.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -243,9 +244,9 @@ describe('action collection', () => { createTest('do not consider a click to change the value of a "range" input as "dead_click"') .withRum({ trackUserInteractions: true }) .withBody(html` `) - .run(async ({ intakeRegistry }) => { - const input = await $('input') - await input.click({ x: 10 }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + const input = page.locator('input') + await input.click({ position: { x: 10, y: 0 } }) await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -256,8 +257,8 @@ describe('action collection', () => { createTest('consider a click on an already checked "radio" input as "dead_click"') .withRum({ trackUserInteractions: true }) .withBody(html` `) - .run(async ({ intakeRegistry }) => { - const input = await $('input') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const input = page.locator('input') await input.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -269,8 +270,8 @@ describe('action collection', () => { createTest('do not consider a click on text input as "dead_click"') .withRum({ trackUserInteractions: true }) .withBody(html` `) - .run(async ({ intakeRegistry }) => { - const input = await $('input') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const input = page.locator('input') await input.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -292,8 +293,8 @@ describe('action collection', () => { `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() await flushEvents() @@ -316,9 +317,11 @@ describe('action collection', () => { `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') - await Promise.all([button.click(), button.click(), button.click()]) + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') + await button.click() + await button.click() + await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -338,14 +341,9 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const windowHandle = await browser.getWindowHandle() - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await button.click() - // Ideally, we would close the newly created window. But on Safari desktop (at least), it is - // not possible to do so: calling `browser.closeWindow()` is failing with "no such window: - // unknown error". Instead, just switch back to the original window. - await browser.switchToWindow(windowHandle) await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -365,8 +363,8 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const button = page.locator('button') await Promise.all([button.click(), button.click(), button.click()]) await flushEvents() const actionEvents = intakeRegistry.rumActionEvents @@ -386,25 +384,23 @@ describe('action collection', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ intakeRegistry, flushEvents, withBrowserLogs, page }) => { + const button = page.locator('button') await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents.length).toBe(1) - expect(actionEvents[0].action.frustration!.type).toEqual( - jasmine.arrayWithExactContents(['error_click', 'dead_click']) - ) + expect(actionEvents[0].action.frustration!.type).toStrictEqual(['error_click', 'dead_click']) expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(2) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) - // We don't use the wdio's `$('button').click()` here because it makes the test slower + // We don't use the playwright's `page.locator('button').click()` here because it makes the test slower createTest('dont crash when clicking on a button') .withRum({ trackUserInteractions: true }) .withBody(html` @@ -426,11 +422,11 @@ describe('action collection', () => { click() } - window.open('foo') + window.open('/empty') `) - .run(async () => { - await withBrowserLogs((logs) => { + .run(({ withBrowserLogs }) => { + withBrowserLogs((logs) => { // A failing test would have a log with message "Uncaught RangeError: Maximum call stack size exceeded" expect(logs).toEqual([]) }) From 6abdc9a3c32d5f8f3472cf17db997d2bbc8acbd0 Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 24 Jan 2025 14:33:48 +0000 Subject: [PATCH 04/68] Migrate vitals.scenario.ts to Playwright --- playwright.config.ts | 2 +- test/e2e/scenario/rum/vitals.scenario.ts | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 8f7417228d..e5da6539d9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,7 +13,7 @@ import { defineConfig, devices } from '@playwright/test' */ export default defineConfig({ testDir: './test/e2e/scenario', - testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts', '**/actions.scenario.ts'], + testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts', '**/actions.scenario.ts', '**/vitals.scenario.ts'], /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/test/e2e/scenario/rum/vitals.scenario.ts b/test/e2e/scenario/rum/vitals.scenario.ts index 93d5a65d2e..f9eb5923aa 100644 --- a/test/e2e/scenario/rum/vitals.scenario.ts +++ b/test/e2e/scenario/rum/vitals.scenario.ts @@ -1,20 +1,23 @@ -import { createTest, flushEvents } from '../../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest } from '../../lib/framework' -describe('vital collection', () => { +test.describe('vital collection', () => { createTest('send custom duration vital') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { + .run(async ({ flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { const vital = window.DD_RUM!.startDurationVital('foo') - setTimeout(() => { - window.DD_RUM!.stopDurationVital(vital) - done() - }, 5) + return new Promise((resolve) => { + setTimeout(() => { + window.DD_RUM!.stopDurationVital(vital) + resolve() + }, 5) + }) }) await flushEvents() expect(intakeRegistry.rumVitalEvents.length).toBe(1) expect(intakeRegistry.rumVitalEvents[0].vital.name).toEqual('foo') - expect(intakeRegistry.rumVitalEvents[0].vital.duration).toEqual(jasmine.any(Number)) + expect(intakeRegistry.rumVitalEvents[0].vital.duration).toEqual(expect.any(Number)) }) }) From 29f4b96661aeb3335759b053ab43a9d618e213b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 24 Jan 2025 15:38:01 +0100 Subject: [PATCH 05/68] =?UTF-8?q?=E2=9C=85=20microfrontend=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 8 ++++- test/e2e/lib/framework/createTest.ts | 7 ++-- test/e2e/scenario/microfrontend.scenario.ts | 38 ++++++++++----------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index e5da6539d9..c369a63d08 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -13,7 +13,13 @@ import { defineConfig, devices } from '@playwright/test' */ export default defineConfig({ testDir: './test/e2e/scenario', - testMatch: ['**/init.scenario.ts', '**/logs.scenario.ts', '**/actions.scenario.ts', '**/vitals.scenario.ts'], + testMatch: [ + '**/init.scenario.ts', + '**/logs.scenario.ts', + '**/actions.scenario.ts', + '**/vitals.scenario.ts', + '**/microfrontend.scenario.ts', + ], /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 319477930e..d3b5670847 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -231,8 +231,11 @@ function createTestContext( browserContext, browserName, withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => { - cb(browserLogsManager.get()) - browserLogsManager.clear() + try { + cb(browserLogsManager.get()) + } finally { + browserLogsManager.clear() + } }, flushBrowserLogs: () => { browserLogsManager.clear() diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts index 71a593d8fc..0e6cd72a91 100644 --- a/test/e2e/scenario/microfrontend.scenario.ts +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -1,7 +1,7 @@ import type { RumEvent, RumEventDomainContext, RumInitConfiguration } from '@datadog/browser-rum-core' import type { LogsEvent, LogsInitConfiguration, LogsEventDomainContext } from '@datadog/browser-logs' -import { flushBrowserLogs, withBrowserLogs } from '../lib/helpers/browser' -import { flushEvents, createTest } from '../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest } from '../lib/framework' const HANDLING_STACK_REGEX = /^Error: \n\s+at testHandlingStack @/ @@ -28,7 +28,7 @@ const LOGS_CONFIG: Partial = { }, } -describe('microfrontend', () => { +test.describe('microfrontend', () => { createTest('expose handling stack for fetch requests') .withRum(RUM_CONFIG) .withRumInit((configuration) => { @@ -42,7 +42,7 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'fetch') @@ -64,7 +64,7 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'xhr') @@ -84,7 +84,7 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const event = intakeRegistry.rumActionEvents[0] @@ -104,7 +104,7 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const event = intakeRegistry.rumErrorEvents[0] @@ -124,21 +124,21 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, withBrowserLogs }) => { await flushEvents() const event = intakeRegistry.rumErrorEvents[0] - await withBrowserLogs((logs) => { + withBrowserLogs((logs) => { expect(logs.length).toBe(1) - expect(logs[0].message).toMatch(/"foo"$/) + expect(logs[0].message).toMatch(/foo$/) // TODO(playwright migration): it looks like chrome is logging the string without double quotes, but some other browser might }) expect(event).toBeTruthy() expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) }) - describe('console apis', () => { + test.describe('console apis', () => { createTest('expose handling stack for console.log') .withLogs(LOGS_CONFIG) .withLogsInit((configuration) => { @@ -150,21 +150,21 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, flushBrowserLogs }) => { await flushEvents() const event = intakeRegistry.logsEvents[0] - await flushBrowserLogs() + flushBrowserLogs() expect(event).toBeTruthy() expect(event?.context).toEqual({ - handlingStack: jasmine.stringMatching(HANDLING_STACK_REGEX), + handlingStack: expect.stringMatching(HANDLING_STACK_REGEX), }) }) }) - describe('logger apis', () => { + test.describe('logger apis', () => { createTest('expose handling stack for DD_LOGS.logger.log') .withLogs(LOGS_CONFIG) .withLogsInit((configuration) => { @@ -176,16 +176,16 @@ describe('microfrontend', () => { testHandlingStack() }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, flushBrowserLogs }) => { await flushEvents() const event = intakeRegistry.logsEvents[0] - await flushBrowserLogs() + flushBrowserLogs() expect(event).toBeTruthy() expect(event?.context).toEqual({ - handlingStack: jasmine.stringMatching(HANDLING_STACK_REGEX), + handlingStack: expect.stringMatching(HANDLING_STACK_REGEX), }) }) }) @@ -205,7 +205,7 @@ describe('microfrontend', () => { }, }) }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const viewEvent = intakeRegistry.rumViewEvents[0] From d4ce59c2debd9e1dd981d8859da0ea32b29acf65 Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 24 Jan 2025 14:49:01 +0000 Subject: [PATCH 06/68] Migrate views.scenario.ts to Playwright --- playwright.config.ts | 5 ++- test/e2e/scenario/rum/views.scenario.ts | 53 ++++++++++++++----------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index c369a63d08..f09cc46f68 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -14,11 +14,12 @@ import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './test/e2e/scenario', testMatch: [ + '**/actions.scenario.ts', '**/init.scenario.ts', '**/logs.scenario.ts', - '**/actions.scenario.ts', - '**/vitals.scenario.ts', '**/microfrontend.scenario.ts', + '**/views.scenario.ts', + '**/vitals.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/rum/views.scenario.ts b/test/e2e/scenario/rum/views.scenario.ts index be757269b5..19b82ebed9 100644 --- a/test/e2e/scenario/rum/views.scenario.ts +++ b/test/e2e/scenario/rum/views.scenario.ts @@ -1,10 +1,10 @@ -import { createTest, flushEvents, html } from '../../lib/framework' -import { getBrowserName } from '../../lib/helpers/browser' +import { test, expect } from '@playwright/test' +import { createTest, html } from '../../lib/framework' -describe('rum views', () => { +test.describe('rum views', () => { createTest('send performance timings along the view events') .withRum() - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() const viewEvent = intakeRegistry.rumViewEvents[0] expect(viewEvent).toBeDefined() @@ -15,31 +15,36 @@ describe('rum views', () => { expect(viewEvent.view.load_event).toBeGreaterThan(0) }) - // When run via WebDriver, Safari <= 14 (at least) have an issue with `event.timeStamp`, - // so the 'first-input' polyfill is ignoring it and doesn't send a performance entry. - // See https://bugs.webkit.org/show_bug.cgi?id=211101 - if (getBrowserName() !== 'safari') { - createTest('send performance first input delay') - .withRum() - .withBody(html` `) - .run(async ({ intakeRegistry }) => { - await (await $('button')).click() - await flushEvents() - const viewEvent = intakeRegistry.rumViewEvents[0] - expect(viewEvent).toBeDefined() - expect(viewEvent.view.first_input_delay).toBeGreaterThanOrEqual(0) - }) - } + createTest('send performance first input delay') + .withRum() + .withBody(html` `) + .run(async ({ browserName, flushEvents, intakeRegistry, page }) => { + test.skip( + browserName === 'webkit', + ` + // When run via WebDriver, Safari <= 14 (at least) have an issue with 'event.timeStamp', + // so the 'first-input' polyfill is ignoring it and doesn't send a performance entry. + // See https://bugs.webkit.org/show_bug.cgi?id=211101 + ` + ) + const button = page.locator('button') + await button.click() + await flushEvents() + const viewEvent = intakeRegistry.rumViewEvents[0] + expect(viewEvent).toBeDefined() + expect(viewEvent.view.first_input_delay).toBeGreaterThanOrEqual(0) + }) - describe('anchor navigation', () => { + test.describe('anchor navigation', () => { createTest("don't create a new view when it is an Anchor navigation") .withRum() .withBody(html` anchor link
`) - .run(async ({ intakeRegistry }) => { - await (await $('a')).click() + .run(async ({ flushEvents, intakeRegistry, page }) => { + const anchor = page.locator('a') + await anchor.click() await flushEvents() const viewEvents = intakeRegistry.rumViewEvents @@ -50,8 +55,8 @@ describe('rum views', () => { createTest('create a new view on hash change') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { window.location.hash = '#bar' }) From f5dd46ffa627b9b82a60d8c68ef7d5fe0204b21a Mon Sep 17 00:00:00 2001 From: zcy Date: Fri, 24 Jan 2025 15:51:21 +0100 Subject: [PATCH 07/68] Use puppeteer directly for s8sInject.scenario --- test/e2e/scenario/rum/s8sInject.scenario.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/scenario/rum/s8sInject.scenario.ts b/test/e2e/scenario/rum/s8sInject.scenario.ts index b13c1d76fc..a6dbe337f6 100644 --- a/test/e2e/scenario/rum/s8sInject.scenario.ts +++ b/test/e2e/scenario/rum/s8sInject.scenario.ts @@ -1,6 +1,7 @@ import * as fs from 'fs' import { RUM_BUNDLE } from '../../lib/framework' import { APPLICATION_ID, CLIENT_TOKEN } from '../../lib/helpers/configuration' +import puppeteer from 'puppeteer' describe('Inject RUM with Puppeteer', () => { // S8s tests inject RUM with puppeteer evaluateOnNewDocument @@ -12,7 +13,7 @@ describe('Inject RUM with Puppeteer', () => { async function injectRumWithPuppeteer() { const ddRUM = fs.readFileSync(RUM_BUNDLE, 'utf8') - const puppeteerBrowser = await browser.getPuppeteer() + const puppeteerBrowser = await puppeteer.launch({ headless: false, devtools: true }) let injected = true await browser.call(async () => { @@ -35,7 +36,7 @@ async function injectRumWithPuppeteer() { injected = false } }) - await page.goto('https://webdriver.io') + await page.goto('https://example.com') }) return injected } From 28b3832cd631a15129d1cb1c13eddee9b8e3956e Mon Sep 17 00:00:00 2001 From: Nicolas Ulrich Date: Fri, 24 Jan 2025 15:59:41 +0100 Subject: [PATCH 08/68] =?UTF-8?q?=E2=9C=85=20sessionStore=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/scenario/sessionStore.scenario.ts | 60 +++++++++++----------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index f09cc46f68..47002771fc 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ '**/init.scenario.ts', '**/logs.scenario.ts', '**/microfrontend.scenario.ts', + '**/sessionStore.scenario.ts', '**/views.scenario.ts', '**/vitals.scenario.ts', ], diff --git a/test/e2e/scenario/sessionStore.scenario.ts b/test/e2e/scenario/sessionStore.scenario.ts index 955ee70042..c011729082 100644 --- a/test/e2e/scenario/sessionStore.scenario.ts +++ b/test/e2e/scenario/sessionStore.scenario.ts @@ -1,20 +1,20 @@ import { SESSION_STORE_KEY } from '@datadog/browser-core' import { createTest } from '../lib/framework' +import { test, expect, BrowserContext, Page } from '@playwright/test' const DISABLE_LOCAL_STORAGE = '' const DISABLE_COOKIES = '' const SESSION_ID_REGEX = /(? { - describe('Cookies', () => { +test.describe('Session Stores', () => { + test.describe('Cookies', () => { createTest('uses cookies to store the session') .withLogs() .withRum() - .run(async () => { - const cookieSessionId = await getSessionIdFromCookie() - - const logsContext = await browser.execute(() => window.DD_LOGS?.getInternalContext()) - const rumContext = await browser.execute(() => window.DD_RUM?.getInternalContext()) + .run(async ({ browserContext, page }) => { + const cookieSessionId = await getSessionIdFromCookie(browserContext) + const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) + const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) expect(logsContext?.session_id).toBe(cookieSessionId) expect(rumContext?.session_id).toBe(cookieSessionId) @@ -24,24 +24,24 @@ describe('Session Stores', () => { .withLogs() .withRum() .withHead(DISABLE_COOKIES) - .run(async () => { - const logsContext = await browser.execute(() => window.DD_LOGS?.getInternalContext()) - const rumContext = await browser.execute(() => window.DD_RUM?.getInternalContext()) + .run(async ({ browserContext, page }) => { + const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) + const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) - expect(logsContext).not.toBeNull() - expect(rumContext).toBeNull() + expect(logsContext).not.toBeUndefined() + expect(rumContext).toBeUndefined() }) }) - describe('Local Storage', () => { + test.describe('Local Storage', () => { createTest('uses localStorage to store the session') .withLogs({ sessionPersistence: 'local-storage' }) .withRum({ sessionPersistence: 'local-storage' }) - .run(async () => { - const sessionId = await getSessionIdFromLocalStorage() + .run(async ({ page }) => { + const sessionId = await getSessionIdFromLocalStorage(page) - const logsContext = await browser.execute(() => window.DD_LOGS?.getInternalContext()) - const rumContext = await browser.execute(() => window.DD_RUM?.getInternalContext()) + const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) + const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) expect(logsContext?.session_id).toBe(sessionId) expect(rumContext?.session_id).toBe(sessionId) @@ -51,12 +51,12 @@ describe('Session Stores', () => { .withLogs({ sessionPersistence: 'local-storage' }) .withRum({ sessionPersistence: 'local-storage' }) .withHead(DISABLE_LOCAL_STORAGE) - .run(async () => { - const logsContext = await browser.execute(() => window.DD_LOGS?.getInternalContext()) - const rumContext = await browser.execute(() => window.DD_RUM?.getInternalContext()) + .run(async ({ page }) => { + const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) + const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) - expect(logsContext).not.toBeNull() - expect(rumContext).toBeNull() + expect(logsContext).not.toBeUndefined() + expect(rumContext).toBeUndefined() }) }) @@ -64,23 +64,23 @@ describe('Session Stores', () => { .withLogs({ allowFallbackToLocalStorage: true }) .withRum({ allowFallbackToLocalStorage: true }) .withHead(DISABLE_COOKIES) - .run(async () => { - const sessionId = await getSessionIdFromLocalStorage() + .run(async ({ page }) => { + const sessionId = await getSessionIdFromLocalStorage(page) - const logsContext = await browser.execute(() => window.DD_LOGS?.getInternalContext()) - const rumContext = await browser.execute(() => window.DD_RUM?.getInternalContext()) + const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) + const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) expect(logsContext?.session_id).toBe(sessionId) expect(rumContext?.session_id).toBe(sessionId) }) }) -async function getSessionIdFromLocalStorage(): Promise { - const sessionStateString = await browser.execute((key) => window.localStorage.getItem(key), SESSION_STORE_KEY) +async function getSessionIdFromLocalStorage(page: Page): Promise { + const sessionStateString = await page.evaluate((key) => window.localStorage.getItem(key), SESSION_STORE_KEY) return sessionStateString?.match(SESSION_ID_REGEX)?.[1] } -async function getSessionIdFromCookie(): Promise { - const [cookie] = await browser.getCookies([SESSION_STORE_KEY]) +async function getSessionIdFromCookie(browserContext: BrowserContext): Promise { + const [cookie] = await browserContext.cookies() return cookie.value.match(SESSION_ID_REGEX)?.[1] } From f4f77ee29b18f66a56bcb1f9b7108408952c92c4 Mon Sep 17 00:00:00 2001 From: zcy Date: Fri, 24 Jan 2025 16:03:04 +0100 Subject: [PATCH 09/68] migrate s8sInject.scenario to playwright --- playwright.config.ts | 1 + test/e2e/scenario/rum/s8sInject.scenario.ts | 26 ++++++++++----------- test/e2e/wdio.bs.conf.ts | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 47002771fc..9a670d6d9b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -21,6 +21,7 @@ export default defineConfig({ '**/sessionStore.scenario.ts', '**/views.scenario.ts', '**/vitals.scenario.ts', + '**/s8sInject.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/rum/s8sInject.scenario.ts b/test/e2e/scenario/rum/s8sInject.scenario.ts index a6dbe337f6..5570316034 100644 --- a/test/e2e/scenario/rum/s8sInject.scenario.ts +++ b/test/e2e/scenario/rum/s8sInject.scenario.ts @@ -2,10 +2,11 @@ import * as fs from 'fs' import { RUM_BUNDLE } from '../../lib/framework' import { APPLICATION_ID, CLIENT_TOKEN } from '../../lib/helpers/configuration' import puppeteer from 'puppeteer' +import { test, expect } from '@playwright/test' -describe('Inject RUM with Puppeteer', () => { +test.describe('Inject RUM with Puppeteer', () => { // S8s tests inject RUM with puppeteer evaluateOnNewDocument - it('should not throw error in chrome', async () => { + test('should not throw error in chrome', async () => { const isInjected = await injectRumWithPuppeteer() expect(isInjected).toBe(true) }) @@ -16,10 +17,9 @@ async function injectRumWithPuppeteer() { const puppeteerBrowser = await puppeteer.launch({ headless: false, devtools: true }) let injected = true - await browser.call(async () => { - const page = await puppeteerBrowser.newPage() - await page.evaluateOnNewDocument( - ` + const page = await puppeteerBrowser.newPage() + await page.evaluateOnNewDocument( + ` if (location.href !== 'about:blank') { ${ddRUM} window.DD_RUM._setDebug(true) @@ -30,13 +30,13 @@ async function injectRumWithPuppeteer() { window.DD_RUM.startView() } ` - ) - page.on('console', (msg) => { - if (msg.type() === 'error') { - injected = false - } - }) - await page.goto('https://example.com') + ) + page.on('console', (msg) => { + if (msg.type() === 'error') { + injected = false + } }) + await page.goto('https://example.com') + return injected } diff --git a/test/e2e/wdio.bs.conf.ts b/test/e2e/wdio.bs.conf.ts index 3e369cd75f..2f598fe3c5 100644 --- a/test/e2e/wdio.bs.conf.ts +++ b/test/e2e/wdio.bs.conf.ts @@ -7,7 +7,7 @@ export const config: Options.Testrunner = { ...baseConfig, specFileRetries: 1, - exclude: [...baseConfig.exclude!, './scenario/rum/s8sInject.scenario.ts'], + exclude: [...baseConfig.exclude!], capabilities: browserConfigurations.map((configuration) => // See https://www.browserstack.com/automate/capabilities?tag=selenium-4 // Make sure to look at the "W3C Protocol" tab From e7ee76b229ce0530e23dbe5d1060ba04fac5565f Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 24 Jan 2025 15:03:46 +0000 Subject: [PATCH 10/68] Migrate eventBridge.scenario.ts to Playwright --- playwright.config.ts | 1 + test/e2e/scenario/eventBridge.scenario.ts | 38 +++++++++++------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 9a670d6d9b..5703491e47 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ testDir: './test/e2e/scenario', testMatch: [ '**/actions.scenario.ts', + '**/eventBridge.scenario.ts', '**/init.scenario.ts', '**/logs.scenario.ts', '**/microfrontend.scenario.ts', diff --git a/test/e2e/scenario/eventBridge.scenario.ts b/test/e2e/scenario/eventBridge.scenario.ts index 59fd301082..a9d934f809 100644 --- a/test/e2e/scenario/eventBridge.scenario.ts +++ b/test/e2e/scenario/eventBridge.scenario.ts @@ -1,7 +1,7 @@ -import { flushBrowserLogs } from '../lib/helpers/browser' -import { createTest, flushEvents, html } from '../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest, html } from '../lib/framework' -describe('bridge present', () => { +test.describe('bridge present', () => { createTest('send action') .withRum({ trackUserInteractions: true }) .withEventBridge() @@ -14,11 +14,11 @@ describe('bridge present', () => { }) `) - .run(async ({ intakeRegistry }) => { - const button = await $('button') + .run(async ({ flushEvents, intakeRegistry, page }) => { + const button = page.locator('button') await button.click() // wait for click chain to close - await browser.pause(1000) + await page.waitForTimeout(1000) await flushEvents() expect(intakeRegistry.rumActionEvents.length).toBe(1) @@ -28,12 +28,12 @@ describe('bridge present', () => { createTest('send error') .withRum() .withEventBridge() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ flushBrowserLogs, flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { console.error('oh snap') }) - await flushBrowserLogs() + flushBrowserLogs() await flushEvents() expect(intakeRegistry.rumErrorEvents.length).toBe(1) @@ -43,7 +43,7 @@ describe('bridge present', () => { createTest('send resource') .withRum() .withEventBridge() - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.rumResourceEvents.length).toBeGreaterThan(0) @@ -53,7 +53,7 @@ describe('bridge present', () => { createTest('send view') .withRum() .withEventBridge() - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.rumViewEvents.length).toBeGreaterThan(0) @@ -63,8 +63,8 @@ describe('bridge present', () => { createTest('forward telemetry to the bridge') .withLogs() .withEventBridge() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { const context = { get foo() { throw new window.Error('bar') @@ -82,8 +82,8 @@ describe('bridge present', () => { createTest('forward logs to the bridge') .withLogs() .withEventBridge() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { window.DD_LOGS!.logger.log('hello') }) await flushEvents() @@ -95,7 +95,7 @@ describe('bridge present', () => { createTest('send records to the bridge') .withRum() .withEventBridge() - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.replayRecords.length).toBeGreaterThan(0) @@ -105,12 +105,12 @@ describe('bridge present', () => { createTest('do not send records when the recording is stopped') .withRum() .withEventBridge() - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry, page }) => { // wait for recorder to be properly started - await browser.pause(200) + await page.waitForTimeout(200) const preStopRecordsCount = intakeRegistry.replayRecords.length - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.stopSessionReplayRecording() // trigger a new record From b79fdef289c35de14101cd47219de4462b3811a7 Mon Sep 17 00:00:00 2001 From: Nicolas Ulrich Date: Fri, 24 Jan 2025 16:22:14 +0100 Subject: [PATCH 11/68] =?UTF-8?q?=E2=9C=85=20trackingConsent=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/scenario/trackingConsent.scenario.ts | 61 +++++++++++-------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 5703491e47..83777e1f9a 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -23,6 +23,7 @@ export default defineConfig({ '**/views.scenario.ts', '**/vitals.scenario.ts', '**/s8sInject.scenario.ts', + '**/trackingConsent.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index deadf16d6f..7af00d5596 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -1,52 +1,53 @@ +import { SessionState } from '@datadog/browser-core' import { createTest, flushEvents } from '../lib/framework' -import { findSessionCookie } from '../lib/helpers/session' +import { test, expect, BrowserContext } from '@playwright/test' -describe('tracking consent', () => { - describe('RUM', () => { +test.describe('tracking consent', () => { + test.describe('RUM', () => { createTest('does not start the SDK if tracking consent is not given at init') .withRum({ trackingConsent: 'not-granted' }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, browserContext }) => { await flushEvents() expect(intakeRegistry.isEmpty).toBe(true) - expect(await findSessionCookie()).toBeUndefined() + expect(await findSessionCookie(browserContext)).toBeUndefined() }) createTest('starts the SDK once tracking consent is granted') .withRum({ trackingConsent: 'not-granted' }) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await page.evaluate(() => { window.DD_RUM!.setTrackingConsent('granted') }) await flushEvents() expect(intakeRegistry.isEmpty).toBe(false) - expect(await findSessionCookie()).toBeDefined() + expect(await findSessionCookie(browserContext)).toBeDefined() }) createTest('stops sending events if tracking consent is revoked') .withRum({ trackUserInteractions: true }) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await page.evaluate(() => { window.DD_RUM!.setTrackingConsent('not-granted') }) - const htmlElement = await $('html') + const htmlElement = page.locator('html') await htmlElement.click() await flushEvents() expect(intakeRegistry.rumActionEvents).toEqual([]) - expect((await findSessionCookie())?.isExpired).toEqual('1') + expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') }) createTest('starts a new session when tracking consent is granted again') .withRum() - .run(async ({ intakeRegistry }) => { - const initialSessionId = await findSessionCookie() + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + const initialSessionId = await findSessionCookie(browserContext) - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.setTrackingConsent('not-granted') window.DD_RUM!.setTrackingConsent('granted') }) @@ -57,7 +58,7 @@ describe('tracking consent', () => { const lastView = intakeRegistry.rumViewEvents.at(-1)! expect(firstView.session.id).not.toEqual(lastView.session.id) expect(firstView.view.id).not.toEqual(lastView.view.id) - expect(await findSessionCookie()).not.toEqual(initialSessionId) + expect(await findSessionCookie(browserContext)).not.toEqual(initialSessionId) }) createTest('using setTrackingConsent before init overrides the init parameter') @@ -66,35 +67,47 @@ describe('tracking consent', () => { window.DD_RUM!.setTrackingConsent('granted') window.DD_RUM!.init(configuration) }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, browserContext }) => { await flushEvents() expect(intakeRegistry.isEmpty).toBe(false) - expect(await findSessionCookie()).toBeDefined() + expect(await findSessionCookie(browserContext)).toBeDefined() }) }) - describe('Logs', () => { + test.describe('Logs', () => { createTest('does not start the SDK if tracking consent is not given at init') .withLogs({ trackingConsent: 'not-granted' }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, browserContext }) => { await flushEvents() expect(intakeRegistry.isEmpty).toBe(true) - expect(await findSessionCookie()).toBeUndefined() + expect(await findSessionCookie(browserContext)).toBeUndefined() }) createTest('starts the SDK once tracking consent is granted') .withLogs({ trackingConsent: 'not-granted' }) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await page.evaluate(() => { window.DD_LOGS!.setTrackingConsent('granted') }) await flushEvents() expect(intakeRegistry.isEmpty).toBe(false) - expect(await findSessionCookie()).toBeDefined() + expect(await findSessionCookie(browserContext)).toBeDefined() }) }) }) + +// TODO: use lib/helper/session when sessions.scenario is migrated +export async function findSessionCookie(browserContext: BrowserContext) { + const cookies = await browserContext.cookies() + // In some case, the session cookie is returned but with an empty value. Let's consider it expired + // in this case. + const rawValue = cookies[0]?.value + if (!rawValue) { + return + } + return Object.fromEntries(rawValue.split('&').map((part: string) => part.split('='))) as SessionState +} From 98d480830c4bc0943c583385b1993e56bc7de2f8 Mon Sep 17 00:00:00 2001 From: Nicolas Ulrich Date: Fri, 24 Jan 2025 16:30:40 +0100 Subject: [PATCH 12/68] =?UTF-8?q?=E2=9C=85=20telemetry=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/scenario/telemetry.scenario.ts | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 83777e1f9a..746ff284d3 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ '**/vitals.scenario.ts', '**/s8sInject.scenario.ts', '**/trackingConsent.scenario.ts', + '**/telemetry.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/telemetry.scenario.ts b/test/e2e/scenario/telemetry.scenario.ts index 3246b37e53..eeeb530c0c 100644 --- a/test/e2e/scenario/telemetry.scenario.ts +++ b/test/e2e/scenario/telemetry.scenario.ts @@ -1,11 +1,12 @@ import { bundleSetup, createTest, flushEvents } from '../lib/framework' +import { test, expect } from '@playwright/test' -describe('telemetry', () => { +test.describe('telemetry', () => { createTest('send errors for logs') .withSetup(bundleSetup) .withLogs() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const context = { get foo() { throw new window.Error('expected error') @@ -26,8 +27,8 @@ describe('telemetry', () => { createTest('send errors for RUM') .withSetup(bundleSetup) .withRum() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { const context = { get foo() { throw new window.Error('expected error') @@ -50,7 +51,7 @@ describe('telemetry', () => { .withLogs({ forwardErrorsToLogs: true, }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() expect(intakeRegistry.telemetryConfigurationEvents.length).toBe(1) const event = intakeRegistry.telemetryConfigurationEvents[0] @@ -63,7 +64,7 @@ describe('telemetry', () => { .withRum({ trackUserInteractions: true, }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() expect(intakeRegistry.telemetryConfigurationEvents.length).toBe(1) const event = intakeRegistry.telemetryConfigurationEvents[0] @@ -74,8 +75,8 @@ describe('telemetry', () => { createTest('send usage telemetry for RUM') .withSetup(bundleSetup) .withRum() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { window.DD_RUM!.addAction('foo') }) @@ -89,8 +90,8 @@ describe('telemetry', () => { createTest('send usage telemetry for logs') .withSetup(bundleSetup) .withLogs() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => { window.DD_LOGS!.setTrackingConsent('granted') }) From b120c3161197768fc2335bccb53b2dc3b9c97996 Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 24 Jan 2025 15:33:45 +0000 Subject: [PATCH 13/68] Migrate transport.scenario.ts to Playwright --- playwright.config.ts | 1 + test/e2e/scenario/transport.scenario.ts | 72 +++++++++++++------------ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 746ff284d3..9d6bf18bca 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ '**/logs.scenario.ts', '**/microfrontend.scenario.ts', '**/sessionStore.scenario.ts', + '**/transport.scenario.ts', '**/views.scenario.ts', '**/vitals.scenario.ts', '**/s8sInject.scenario.ts', diff --git a/test/e2e/scenario/transport.scenario.ts b/test/e2e/scenario/transport.scenario.ts index 42ba400dfb..72bc54d78e 100644 --- a/test/e2e/scenario/transport.scenario.ts +++ b/test/e2e/scenario/transport.scenario.ts @@ -1,13 +1,13 @@ -import { createTest, flushEvents } from '../lib/framework' -import { getBrowserName, getPlatformName, withBrowserLogs } from '../lib/helpers/browser' +import { test, expect } from '@playwright/test' +import { createTest } from '../lib/framework' -describe('transport', () => { - describe('data compression', () => { +test.describe('transport', () => { + test.describe('data compression', () => { createTest('send RUM data compressed') .withRum({ compressIntakeRequests: true, }) - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.rumRequests.length).toBe(2) @@ -16,39 +16,45 @@ describe('transport', () => { const deflateRequest = intakeRegistry.rumRequests.find((request) => request.encoding === 'deflate') // The last view update should be sent without compression - expect(plainRequest!.events).toEqual([ - jasmine.objectContaining({ - type: 'view', - }), - ]) + expect(plainRequest?.events).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'view', + }), + ]) + ) // Other data should be sent encoded expect(deflateRequest!.events.length).toBeGreaterThan(0) }) - // Ignore this test on Safari desktop and Firefox because the Worker actually works even with - // CSP restriction. - // TODO: Remove this condition when upgrading to Safari 15 and Firefox 99 - if (!((getBrowserName() === 'safari' && getPlatformName() === 'macos') || getBrowserName() === 'firefox')) { - createTest("displays a message if the worker can't be started") - .withRum({ - compressIntakeRequests: true, - }) - .withBasePath('/no-blob-worker-csp') - .run(async ({ intakeRegistry }) => { - await flushEvents() - - // Some non-deflate request can still be sent because on some browsers the Worker fails - // asynchronously - expect(intakeRegistry.rumRequests.filter((request) => request.encoding === 'deflate').length).toBe(0) - - await withBrowserLogs((logs) => { - const failedToStartLog = logs.find((log) => log.message.includes('Datadog RUM failed to start')) - const cspDocLog = logs.find((log) => log.message.includes('Please make sure CSP')) - expect(failedToStartLog).withContext("'Failed to start' log").toBeTruthy() - expect(cspDocLog).withContext("'CSP doc' log").toBeTruthy() - }) + createTest("displays a message if the worker can't be started") + .withRum({ + compressIntakeRequests: true, + }) + .withBasePath('/no-blob-worker-csp') + .run(async ({ browserName, flushEvents, intakeRegistry, page, withBrowserLogs }) => { + const userAgent = await page.evaluate(() => navigator.userAgent) + test.skip( + browserName === 'firefox' || (browserName === 'webkit' && userAgent.includes('Mac OS X')), + ` + // Ignore this test on Safari desktop and Firefox because the Worker actually works even with + // CSP restriction. + // TODO: Remove this condition when upgrading to Safari 15 and Firefox 99 + ` + ) + await flushEvents() + + // Some non-deflate request can still be sent because on some browsers the Worker fails + // asynchronously + expect(intakeRegistry.rumRequests.filter((request) => request.encoding === 'deflate').length).toBe(0) + + withBrowserLogs((logs) => { + const failedToStartLog = logs.find((log) => log.message.includes('Datadog RUM failed to start')) + const cspDocLog = logs.find((log) => log.message.includes('Please make sure CSP')) + expect(failedToStartLog, "'Failed to start' log").toBeTruthy() + expect(cspDocLog, "'CSP doc' log").toBeTruthy() }) - } + }) }) }) From 1533a3bcf521c8ddaee10fedd2c358491376b320 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 24 Jan 2025 16:44:27 +0100 Subject: [PATCH 14/68] =?UTF-8?q?=E2=9C=85=20add=20resource=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/lib/framework/createTest.ts | 6 +- test/e2e/lib/helpers/browser.ts | 34 ++-- test/e2e/scenario/rum/resources.scenario.ts | 215 +++++++++++--------- 4 files changed, 143 insertions(+), 113 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 9d6bf18bca..ed4ef2adab 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ '**/s8sInject.scenario.ts', '**/trackingConsent.scenario.ts', '**/telemetry.scenario.ts', + '**/resources.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index d3b5670847..095582075a 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -4,8 +4,8 @@ import { DefaultPrivacyLevel } from '@datadog/browser-rum' import type { BrowserContext, Page } from '@playwright/test' import { test, expect } from '@playwright/test' import { getRunId } from '../../../envUtils' -import type { BrowserLog } from '../helpers/browser' -import { BrowserLogsManager, deleteAllCookies } from '../helpers/browser' +import { BrowserLog } from '../helpers/browser' +import { BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import { IntakeRegistry } from './intakeRegistry' @@ -57,6 +57,7 @@ interface TestContext { flushBrowserLogs: () => void flushEvents: () => Promise deleteAllCookies: () => Promise + sendXhr: (url: string, headers: string[][]) => Promise } type TestRunner = (testContext: TestContext) => Promise | void @@ -242,6 +243,7 @@ function createTestContext( }, flushEvents: () => flushEvents(page), deleteAllCookies: () => deleteAllCookies(browserContext), + sendXhr: (url: string, headers: string[][]) => sendXhr(page, url, headers), } } diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 192af52e79..62005578f9 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -1,5 +1,7 @@ import * as os from 'os' -import type { BrowserContext } from '@playwright/test' +import type { BrowserContext, Page } from '@playwright/test' +import { url } from 'inspector' +import { resolve } from 'dns' // To keep tests sane, ensure we got a fixed list of possible platforms and browser names. const validPlatformNames = ['windows', 'macos', 'linux', 'ios', 'android'] as const @@ -99,23 +101,23 @@ export function setCookie(name: string, value: string, expiresDelay: number = 0) ) } -export async function sendXhr(url: string, headers: string[][] = []): Promise { +export async function sendXhr(page: Page, url: string, headers: string[][] = []): Promise { type State = { state: 'success'; response: string } | { state: 'error' } - const result: State = await browser.executeAsync( - (url, headers, done) => { - const xhr = new XMLHttpRequest() - let state: State = { state: 'error' } - xhr.addEventListener('load', () => { - state = { state: 'success', response: xhr.response as string } - }) - xhr.addEventListener('loadend', () => done(state)) - xhr.open('GET', url) - headers.forEach((header) => xhr.setRequestHeader(header[0], header[1])) - xhr.send() - }, - url, - headers + const result: State = await page.evaluate( + ([url, headers]) => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + let state: State = { state: 'error' } + xhr.addEventListener('load', () => { + state = { state: 'success', response: xhr.response as string } + }) + xhr.addEventListener('loadend', () => resolve(state)) + xhr.open('GET', url) + headers.forEach((header) => xhr.setRequestHeader(header[0], header[1])) + xhr.send() + }), + [url, headers] as const ) if (result.state === 'error') { diff --git a/test/e2e/scenario/rum/resources.scenario.ts b/test/e2e/scenario/rum/resources.scenario.ts index a7081344c5..97c0d0fc1a 100644 --- a/test/e2e/scenario/rum/resources.scenario.ts +++ b/test/e2e/scenario/rum/resources.scenario.ts @@ -1,14 +1,14 @@ +import { test, expect } from '@playwright/test' import type { RumResourceEvent } from '@datadog/browser-rum' import type { IntakeRegistry } from '../../lib/framework' -import { flushEvents, bundleSetup, createTest, html } from '../../lib/framework' -import { sendXhr } from '../../lib/helpers/browser' +import { bundleSetup, createTest, html } from '../../lib/framework' const REQUEST_DURATION = 200 -describe('rum resources', () => { +test.describe('rum resources', () => { createTest('track xhr timings') .withRum() - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, sendXhr }) => { await sendXhr(`/ok?duration=${REQUEST_DURATION}`) await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((r) => r.resource.url.includes('/ok'))! @@ -20,7 +20,7 @@ describe('rum resources', () => { createTest('track redirect xhr timings') .withRum() - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, sendXhr }) => { await sendXhr(`/redirect?duration=${REQUEST_DURATION}`) await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((r) => r.resource.url.includes('/redirect'))! @@ -34,7 +34,7 @@ describe('rum resources', () => { createTest("don't track disallowed cross origin xhr timings") .withRum() - .run(async ({ crossOriginUrl, intakeRegistry }) => { + .run(async ({ crossOriginUrl, intakeRegistry, flushEvents, sendXhr }) => { await sendXhr(`${crossOriginUrl}/ok?duration=${REQUEST_DURATION}`) await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((r) => r.resource.url.includes('/ok'))! @@ -47,7 +47,7 @@ describe('rum resources', () => { createTest('track allowed cross origin xhr timings') .withRum() - .run(async ({ crossOriginUrl, intakeRegistry }) => { + .run(async ({ crossOriginUrl, intakeRegistry, flushEvents, sendXhr }) => { await sendXhr(`${crossOriginUrl}/ok?timing-allow-origin=true&duration=${REQUEST_DURATION}`) await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((r) => r.resource.url.includes('/ok'))! @@ -60,7 +60,7 @@ describe('rum resources', () => { createTest('retrieve early requests timings') .withRum() .withHead(html` `) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((event) => event.resource.url.includes('empty.css')) expect(resourceEvent).toBeDefined() @@ -69,7 +69,7 @@ describe('rum resources', () => { createTest('retrieve initial document timings') .withRum() - .run(async ({ baseUrl, intakeRegistry }) => { + .run(async ({ baseUrl, intakeRegistry, flushEvents }) => { await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'document') expect(resourceEvent).toBeDefined() @@ -77,20 +77,23 @@ describe('rum resources', () => { expectToHaveValidTimings(resourceEvent!) }) - describe('XHR abort support', () => { + test.describe('XHR abort support', () => { createTest('track aborted XHR') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const xhr = new XMLHttpRequest() - xhr.open('GET', '/ok?duration=1000') - xhr.send() - setTimeout(() => { - xhr.abort() - done(undefined) - }, 100) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + xhr.open('GET', '/ok?duration=1000') + xhr.send() + setTimeout(() => { + xhr.abort() + resolve(undefined) + }, 100) + }) + ) await flushEvents() @@ -100,14 +103,17 @@ describe('rum resources', () => { createTest('aborting an unsent XHR should be ignored') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const xhr = new XMLHttpRequest() - xhr.open('GET', '/ok') - xhr.abort() - xhr.send() - xhr.addEventListener('loadend', () => done(undefined)) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + xhr.open('GET', '/ok') + xhr.abort() + xhr.send() + xhr.addEventListener('loadend', () => resolve(undefined)) + }) + ) await flushEvents() @@ -117,18 +123,21 @@ describe('rum resources', () => { createTest('aborting an XHR when state becomes DONE and before the loadend event should be ignored') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const xhr = new XMLHttpRequest() - xhr.open('GET', '/ok') - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - xhr.abort() - done(undefined) - } - } - xhr.send() - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + xhr.open('GET', '/ok') + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + xhr.abort() + resolve(undefined) + } + } + xhr.send() + }) + ) await flushEvents() @@ -138,18 +147,21 @@ describe('rum resources', () => { createTest('aborting an XHR after the loadend event should be ignored') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const xhr = new XMLHttpRequest() - xhr.open('GET', '/ok') - xhr.addEventListener('loadend', () => { - setTimeout(() => { - xhr.abort() - done(undefined) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + xhr.open('GET', '/ok') + xhr.addEventListener('loadend', () => { + setTimeout(() => { + xhr.abort() + resolve(undefined) + }) + }) + xhr.send() }) - }) - xhr.send() - }) + ) await flushEvents() @@ -172,21 +184,24 @@ describe('rum resources', () => { } }) - describe('fetch abort support', () => { + test.describe('fetch abort support', () => { createTest('track aborted fetch') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const controller = new AbortController() - fetch('/ok?duration=1000', { signal: controller.signal }).catch(() => { - // ignore abortion error - done(undefined) - }) - setTimeout(() => { - controller.abort() - }, 100) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const controller = new AbortController() + fetch('/ok?duration=1000', { signal: controller.signal }).catch(() => { + // ignore abortion error + resolve(undefined) + }) + setTimeout(() => { + controller.abort() + }, 100) + }) + ) await flushEvents() @@ -198,15 +213,18 @@ describe('rum resources', () => { createTest('track redirect fetch timings') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - fetch('/redirect?duration=200').then( - () => done(undefined), - () => { - throw Error('Issue with fetch call') - } - ) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + fetch('/redirect?duration=200').then( + () => resolve(undefined), + () => { + throw Error('Issue with fetch call') + } + ) + }) + ) await flushEvents() const resourceEvent = intakeRegistry.rumResourceEvents.find((r) => r.resource.url.includes('/redirect'))! expect(resourceEvent).not.toBeUndefined() @@ -220,16 +238,20 @@ describe('rum resources', () => { createTest('track concurrent fetch to same resource') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - Promise.all([fetch('/ok'), fetch('/ok')]) - .then(() => done()) - .catch(() => done()) - }) + .run(async ({ intakeRegistry, flushEvents, page, browserName }) => { + await page.evaluate( + () => + new Promise((resolve) => { + Promise.all([fetch('/ok'), fetch('/ok')]) + .then(() => resolve()) + .catch(() => resolve()) + }) + ) - if (!browser.isChromium) { - pending('Only Chromium based browsers will emit predictable timings events for concurrent fetches') - } + test.skip( + browserName === 'chromium', + 'Only Chromium based browsers will emit predictable timings events for concurrent fetches' + ) await flushEvents() @@ -242,23 +264,26 @@ describe('rum resources', () => { expect(resourceEvents[1]?.resource.size).toBeDefined() }) - describe('support XHRs with same XMLHttpRequest instance', () => { + test.describe('support XHRs with same XMLHttpRequest instance', () => { createTest('track XHRs when calling requests one after another') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { - const xhr = new XMLHttpRequest() - const triggerSecondCall = () => { - xhr.removeEventListener('loadend', triggerSecondCall) - xhr.addEventListener('loadend', () => done(undefined)) - xhr.open('GET', '/ok?duration=100&call=2') - xhr.send() - } - xhr.addEventListener('loadend', triggerSecondCall) - xhr.open('GET', '/ok?duration=100&call=1') - xhr.send() - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate( + () => + new Promise((resolve) => { + const xhr = new XMLHttpRequest() + const triggerSecondCall = () => { + xhr.removeEventListener('loadend', triggerSecondCall) + xhr.addEventListener('loadend', () => resolve(undefined)) + xhr.open('GET', '/ok?duration=100&call=2') + xhr.send() + } + xhr.addEventListener('loadend', triggerSecondCall) + xhr.open('GET', '/ok?duration=100&call=1') + xhr.send() + }) + ) await flushEvents() From 5cff9f26b160de0eb1af9dc2454087768f9c0beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 24 Jan 2025 16:45:02 +0100 Subject: [PATCH 15/68] =?UTF-8?q?=E2=9C=85=20recorder/recorder=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/rum/test/mutationPayloadValidator.ts | 22 +- playwright.config.ts | 1 + .../scenario/recorder/recorder.scenario.ts | 250 +++++++++--------- 3 files changed, 147 insertions(+), 126 deletions(-) diff --git a/packages/rum/test/mutationPayloadValidator.ts b/packages/rum/test/mutationPayloadValidator.ts index f1cdf04a5f..ef528c04f1 100644 --- a/packages/rum/test/mutationPayloadValidator.ts +++ b/packages/rum/test/mutationPayloadValidator.ts @@ -12,6 +12,9 @@ import type { import { findAllIncrementalSnapshots, findFullSnapshot } from './segments' import { findTextNode, findElementWithTagName, findElementWithIdAttribute } from './nodes' +// Should match both jasmine and playwright 'expect' functions +type Expect = (actual: any) => { toEqual(expected: any): void } + interface NodeSelector { // Select the first node with the given tag name from the initial full snapshot tag?: string @@ -121,7 +124,11 @@ export function createMutationPayloadValidator(initialDocument: SerializedNodeWi /** * Validates the mutation payload against the expected text, attribute, add and remove mutations. */ - validate: (payload: BrowserMutationPayload, expected: ExpectedMutationsPayload) => { + validate: ( + payload: BrowserMutationPayload, + expected: ExpectedMutationsPayload, + { expect = globalThis.expect }: { expect?: Expect } = {} + ) => { payload = removeUndefinedValues(payload) expect(payload.adds).toEqual( @@ -201,18 +208,23 @@ export function createMutationPayloadValidator(initialDocument: SerializedNodeWi * Validate the first and only mutation record of a segment against the expected text, attribute, * add and remove mutations. */ -export function createMutationPayloadValidatorFromSegment(segment: BrowserSegment) { +export function createMutationPayloadValidatorFromSegment(segment: BrowserSegment, options?: { expect?: Expect }) { const fullSnapshot = findFullSnapshot(segment)! - expect(fullSnapshot).toBeTruthy() + if (!fullSnapshot) { + throw new Error('Full snapshot not found') + } const mutations = findAllIncrementalSnapshots(segment, IncrementalSource.Mutation) as Array<{ data: BrowserMutationData }> - expect(mutations.length).toBe(1) + if (mutations.length !== 1) { + throw new Error(`Expected 1 mutation, found ${mutations.length}`) + } const mutationPayloadValidator = createMutationPayloadValidator(fullSnapshot.data.node) return { ...mutationPayloadValidator, - validate: (expected: ExpectedMutationsPayload) => mutationPayloadValidator.validate(mutations[0].data, expected), + validate: (expected: ExpectedMutationsPayload) => + mutationPayloadValidator.validate(mutations[0].data, expected, options), } } diff --git a/playwright.config.ts b/playwright.config.ts index ed4ef2adab..b37f2edf47 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -27,6 +27,7 @@ export default defineConfig({ '**/trackingConsent.scenario.ts', '**/telemetry.scenario.ts', '**/resources.scenario.ts', + '**/recorder.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 63ef90340b..5df6ed14d7 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -8,29 +8,30 @@ import { DefaultPrivacyLevel } from '@datadog/browser-rum' import { findElement, findElementWithIdAttribute, + findTextContent, + findElementWithTagName, +} from '@datadog/browser-rum/test/nodes' +import { findFullSnapshot, findIncrementalSnapshot, findAllIncrementalSnapshots, findMeta, - findTextContent, - createMutationPayloadValidatorFromSegment, findAllFrustrationRecords, findMouseInteractionRecords, - findElementWithTagName, -} from '@datadog/browser-rum/test' -import { SESSION_STORE_KEY } from '@datadog/browser-core' -import { flushEvents, createTest, bundleSetup, html } from '../../lib/framework' +} from '@datadog/browser-rum/test/segments' +import { createMutationPayloadValidatorFromSegment } from '@datadog/browser-rum/test/mutationPayloadValidator' +import { test, expect } from '@playwright/test' +import { wait } from '@datadog/browser-core/test/wait' +import { createTest, bundleSetup, html } from '../../lib/framework' -const TIMESTAMP_RE = /^\d{13}$/ const UUID_RE = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/ -describe('recorder', () => { +test.describe('recorder', () => { createTest('record mouse move') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => document.documentElement.outerHTML) - const html = await $('html') - await html.click() + .run(async ({ intakeRegistry, flushEvents, page }) => { + await page.evaluate(() => document.documentElement.outerHTML) + await page.locator('html').click() await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -40,16 +41,16 @@ describe('recorder', () => { segmentFile: { encoding, filename, mimetype }, } = intakeRegistry.replayRequests[0] expect(metadata).toEqual({ - application: { id: jasmine.stringMatching(UUID_RE) }, + application: { id: expect.stringMatching(UUID_RE) }, creation_reason: 'init', - end: jasmine.stringMatching(TIMESTAMP_RE), + end: expect.any(Number), has_full_snapshot: true, - records_count: jasmine.any(Number), - session: { id: jasmine.stringMatching(UUID_RE) }, - start: jasmine.stringMatching(TIMESTAMP_RE), - view: { id: jasmine.stringMatching(UUID_RE) }, - raw_segment_size: jasmine.any(Number), - compressed_segment_size: jasmine.any(Number), + records_count: expect.any(Number), + session: { id: expect.stringMatching(UUID_RE) }, + start: expect.any(Number), + view: { id: expect.stringMatching(UUID_RE) }, + raw_segment_size: expect.any(Number), + compressed_segment_size: expect.any(Number), index_in_view: 0, source: 'browser', }) @@ -58,7 +59,7 @@ describe('recorder', () => { creation_reason: metadata.creation_reason, end: Number(metadata.end), has_full_snapshot: true, - records: jasmine.any(Array), + records: expect.any(Array), records_count: Number(metadata.records_count), session: { id: metadata.session.id }, start: Number(metadata.start), @@ -66,18 +67,19 @@ describe('recorder', () => { index_in_view: 0, source: 'browser', }) - expect(encoding).toEqual(jasmine.any(String)) + expect(encoding).toEqual(expect.any(String)) expect(filename).toBe(`${metadata.session.id}-${metadata.start}`) expect(mimetype).toBe('application/octet-stream') - expect(findMeta(segment)).toBeTruthy('have a Meta record') - expect(findFullSnapshot(segment)).toBeTruthy('have a FullSnapshot record') - expect(findIncrementalSnapshot(segment, IncrementalSource.MouseInteraction)).toBeTruthy( + expect(findMeta(segment), 'have a Meta record').toBeTruthy() + expect(findFullSnapshot(segment), 'have a FullSnapshot record').toBeTruthy() + expect( + findIncrementalSnapshot(segment, IncrementalSource.MouseInteraction), 'have a IncrementalSnapshot/MouseInteraction record' - ) + ).toBeTruthy() }) - describe('full snapshot', () => { + test.describe('full snapshot', () => { createTest('obfuscate elements') .withRum() .withSetup(bundleSetup) @@ -88,7 +90,7 @@ describe('recorder', () => { `) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -120,7 +122,7 @@ describe('recorder', () => { }) }) - describe('mutations observer', () => { + test.describe('mutations observer', () => { createTest('record mutations') .withRum() .withSetup(bundleSetup) @@ -130,8 +132,8 @@ describe('recorder', () => {
  • `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const li = document.createElement('li') const ul = document.querySelector('ul') as HTMLUListElement @@ -146,7 +148,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectNewNode, expectInitialNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) validate({ @@ -174,8 +177,8 @@ describe('recorder', () => {
  • `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const li = document.createElement('li') const ul = document.querySelector('ul') as HTMLUListElement @@ -192,7 +195,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectNewNode, expectInitialNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) validate({ @@ -224,8 +228,8 @@ describe('recorder', () => {
  • `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const li = document.createElement('li') const ul = document.querySelector('ul') as HTMLUListElement @@ -240,7 +244,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectInitialNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) validate({ @@ -269,8 +274,8 @@ describe('recorder', () => { `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { document.querySelector('div')!.setAttribute('foo', 'bar') document.querySelector('li')!.textContent = 'hop' document.querySelector('div')!.appendChild(document.createElement('p')) @@ -294,8 +299,8 @@ describe('recorder', () => { cdefg ` ) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const div = document.querySelector('div')! const p = document.querySelector('p')! const span = document.querySelector('span')! @@ -308,7 +313,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectInitialNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) validate({ adds: [ @@ -343,8 +349,8 @@ describe('recorder', () => { cdefg ` ) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const div = document.createElement('div') const span = document.querySelector('span')! document.body.appendChild(div) @@ -354,7 +360,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectInitialNode, expectNewNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) const div = expectNewNode({ type: NodeType.Element, tagName: 'div' }) @@ -394,8 +401,8 @@ describe('recorder', () => {
    ` ) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const ul = document.querySelector('ul') as HTMLUListElement let count = 3 while (count > 0) { @@ -408,7 +415,8 @@ describe('recorder', () => { await flushEvents() const { validate, expectInitialNode, expectNewNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) const ul = expectInitialNode({ tag: 'ul' }) @@ -437,7 +445,7 @@ describe('recorder', () => { }) }) - describe('input observers', () => { + test.describe('input observers', () => { createTest('record input interactions') .withRum({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, @@ -465,21 +473,21 @@ describe('recorder', () => { `) - .run(async ({ intakeRegistry }) => { - const textInput = await $('#text-input') - await textInput.setValue('test') + .run(async ({ intakeRegistry, page, flushEvents }) => { + const textInput = page.locator('#text-input') + await textInput.pressSequentially('test') - const radioInput = await $('#radio-input') + const radioInput = page.locator('#radio-input') await radioInput.click() - const checkboxInput = await $('#checkbox-input') + const checkboxInput = page.locator('#checkbox-input') await checkboxInput.click() - const textarea = await $('#textarea') - await textarea.setValue('textarea test') + const textarea = page.locator('#textarea') + await textarea.pressSequentially('textarea test') - const select = await $('#select') - await select.selectByAttribute('value', '2') + const select = page.locator('#select') + await select.selectOption({ value: '2' }) await flushEvents() @@ -527,18 +535,18 @@ describe('recorder', () => { `) - .run(async ({ intakeRegistry }) => { - const firstInput = await $('#first') - await firstInput.setValue('foo') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const firstInput = page.locator('#first') + await firstInput.fill('foo') - const secondInput = await $('#second') - await secondInput.setValue('bar') + const secondInput = page.locator('#second') + await secondInput.fill('bar') - const thirdInput = await $('#third') - await thirdInput.setValue('baz') + const thirdInput = page.locator('#third') + await thirdInput.fill('baz') - const fourthInput = await $('#fourth') - await fourthInput.setValue('quux') + const fourthInput = page.locator('#fourth') + await fourthInput.fill('quux') await flushEvents() @@ -558,12 +566,12 @@ describe('recorder', () => { `) - .run(async ({ intakeRegistry }) => { - const firstInput = await $('#by-data-attribute') - await firstInput.setValue('foo') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const firstInput = page.locator('#by-data-attribute') + await firstInput.fill('foo') - const secondInput = await $('#by-classname') - await secondInput.setValue('bar') + const secondInput = page.locator('#by-classname') + await secondInput.fill('bar') await flushEvents() @@ -581,7 +589,7 @@ describe('recorder', () => { }) }) - describe('stylesheet rules observer', () => { + test.describe('stylesheet rules observer', () => { createTest('record dynamic CSS changes') .withRum() .withSetup(bundleSetup) @@ -593,8 +601,8 @@ describe('recorder', () => { } `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { document.styleSheets[0].deleteRule(0) document.styleSheets[0].insertRule('.added {}', 0) }) @@ -631,8 +639,8 @@ describe('recorder', () => { } `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { const supportsRule = document.styleSheets[0].cssRules[0] as CSSGroupingRule const mediaRule = document.styleSheets[0].cssRules[1] as CSSGroupingRule @@ -658,12 +666,12 @@ describe('recorder', () => { }) }) - describe('frustration records', () => { + test.describe('frustration records', () => { createTest('should detect a dead click and match it to mouse interaction record') .withRum({ trackUserInteractions: true }) .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - const html = await $('html') + .run(async ({ intakeRegistry, flushEvents, page }) => { + const html = page.locator('html') await html.click() await flushEvents() @@ -674,7 +682,7 @@ describe('recorder', () => { const frustrationRecords = findAllFrustrationRecords(segment) expect(mouseupRecords.length).toBe(1) - expect(mouseupRecords[0].id).toBeTruthy('mouse interaction record should have an id') + expect(mouseupRecords[0].id, 'mouse interaction record should have an id').toBeTruthy() expect(frustrationRecords.length).toBe(1) expect(frustrationRecords[0].data).toEqual({ frustrationTypes: [FrustrationType.DEAD_CLICK], @@ -692,10 +700,10 @@ describe('recorder', () => { onclick="document.querySelector('#main-div').appendChild(document.createElement('div'));" /> `) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, page, flushEvents }) => { // We don't use the wdio's `$('button').click()` here because the latency of the command is too high and the // clicks won't be recognised as rage clicks. - await browser.execute(() => { + await page.evaluate(() => { const button = document.querySelector('button')! function click() { @@ -732,7 +740,7 @@ describe('recorder', () => { }) }) - describe('scroll positions', () => { + test.describe('scroll positions', () => { createTest('should be recorded across navigation') // to control initial position before recording .withRum({ startSessionReplayRecordingManually: true }) @@ -756,47 +764,47 @@ describe('recorder', () => {
    `) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, page, flushEvents }) => { function scroll({ windowY, containerX }: { windowY: number; containerX: number }) { - return browser.executeAsync( - (windowY, containerX, done) => { - let scrollCount = 0 - - document.addEventListener( - 'scroll', - () => { - scrollCount++ - if (scrollCount === 2) { - // ensure to bypass observer throttling - setTimeout(done, 100) - } - }, - { capture: true, passive: true } - ) - - window.scrollTo(0, windowY) - document.getElementById('container')!.scrollTo(containerX, 0) - }, - windowY, - containerX + return page.evaluate( + ({ windowY, containerX }) => + new Promise((resolve) => { + let scrollCount = 0 + + document.addEventListener( + 'scroll', + () => { + scrollCount++ + if (scrollCount === 2) { + // ensure to bypass observer throttling + setTimeout(resolve, 100) + } + }, + { capture: true, passive: true } + ) + + window.scrollTo(0, windowY) + document.getElementById('container')!.scrollTo(containerX, 0) + }), + { windowY, containerX } ) } // initial scroll positions await scroll({ windowY: 100, containerX: 10 }) - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.startSessionReplayRecording() }) // wait for recorder to be properly started - await browser.pause(200) + await wait(100) // update scroll positions await scroll({ windowY: 150, containerX: 20 }) // trigger new full snapshot - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.startView() }) @@ -825,12 +833,12 @@ describe('recorder', () => { }) }) - describe('recording of sampled out sessions', () => { + test.describe('recording of sampled out sessions', () => { createTest('should not start recording when session is sampled out') .withRum({ sessionReplaySampleRate: 0 }) .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { window.DD_RUM!.startSessionReplayRecording() }) @@ -842,11 +850,11 @@ describe('recorder', () => { createTest('should start recording if forced when session is sampled out') .withRum({ sessionReplaySampleRate: 0 }) .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents, browserContext }) => { + await page.evaluate(() => { window.DD_RUM!.startSessionReplayRecording({ force: true }) }) - const [cookie] = await browser.getCookies([SESSION_STORE_KEY]) + const [cookie] = await browserContext.cookies() expect(cookie.value).toContain('forcedReplay=1') await flushEvents() @@ -858,8 +866,8 @@ describe('recorder', () => { createTest('restarting recording should send a new full snapshot') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, page, flushEvents }) => { + await page.evaluate(() => { window.DD_RUM!.stopSessionReplayRecording() window.DD_RUM!.startSessionReplayRecording() }) @@ -869,17 +877,17 @@ describe('recorder', () => { expect(intakeRegistry.replaySegments.length).toBe(2) const firstSegment = intakeRegistry.replaySegments[0] - expect(findFullSnapshot(firstSegment)).toBeTruthy('first segment have a FullSnapshot record') + expect(findFullSnapshot(firstSegment), 'first segment have a FullSnapshot record').toBeTruthy() const secondSegment = intakeRegistry.replaySegments[1] - expect(findFullSnapshot(secondSegment)).toBeTruthy('second segment have a FullSnapshot record') + expect(findFullSnapshot(secondSegment), 'second segment have a FullSnapshot record').toBeTruthy() }) createTest('workerUrl initialization parameter') .withRum({ workerUrl: '/worker.js' }) .withSetup(bundleSetup) .withBasePath('/no-blob-worker-csp') - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) From faf30a2c25c4d67dcaa7aa4de869428ea970ed1d Mon Sep 17 00:00:00 2001 From: zcy Date: Fri, 24 Jan 2025 17:19:22 +0100 Subject: [PATCH 16/68] =?UTF-8?q?=E2=9C=85=20errors=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/scenario/rum/errors.scenario.ts | 86 ++++++++++++------------ 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index b37f2edf47..c2e51938f9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -28,6 +28,7 @@ export default defineConfig({ '**/telemetry.scenario.ts', '**/resources.scenario.ts', '**/recorder.scenario.ts', + '**/errors.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 6fb7ab360d..56644c66de 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -1,6 +1,7 @@ import type { RumErrorEvent } from '@datadog/browser-rum-core' -import { createTest, flushEvents, html } from '../../lib/framework' -import { getBrowserName, getPlatformName, withBrowserLogs } from '../../lib/helpers/browser' +import { createTest, html } from '../../lib/framework' +import { getBrowserName, getPlatformName } from '../../lib/helpers/browser' +import { test, expect } from '@playwright/test' // Note: using `browser.execute` to throw exceptions may result in "Script error." being reported, // because WDIO is evaluating the script in a different context than the page. @@ -19,12 +20,12 @@ function createBody(errorGenerator: string) { ` } -describe('rum errors', () => { +test.describe('rum errors', () => { createTest('send console.error errors') .withRum() .withBody(createBody('console.error("oh snap")')) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') + .run(async ({ page, intakeRegistry, baseUrl, flushEvents, withBrowserLogs }) => { + const button = await page.locator('button') await button.click() await flushEvents() @@ -43,8 +44,8 @@ describe('rum errors', () => { createTest('pass Error instance to console.error') .withRum() .withBody(createBody('console.error("Foo:", foo())')) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') + .run(async ({ page, flushEvents, intakeRegistry, baseUrl, withBrowserLogs }) => { + const button = await page.locator('button') await button.click() await flushEvents() @@ -64,8 +65,8 @@ describe('rum errors', () => { createTest('send uncaught exceptions') .withRum() .withBody(createBody('throw foo()')) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') + .run(async ({ page, flushEvents, intakeRegistry, baseUrl, withBrowserLogs }) => { + const button = await page.locator('button') await button.click() await flushEvents() @@ -84,8 +85,8 @@ describe('rum errors', () => { createTest('send unhandled rejections') .withRum() .withBody(createBody('Promise.reject(foo())')) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') + .run(async ({ flushEvents, page, intakeRegistry, baseUrl, withBrowserLogs }) => { + const button = await page.locator('button') await button.click() await flushEvents() @@ -104,8 +105,8 @@ describe('rum errors', () => { createTest('send custom errors') .withRum() .withBody(createBody('DD_RUM.addError(foo())')) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') + .run(async ({ flushEvents, page, intakeRegistry, baseUrl, withBrowserLogs }) => { + const button = await page.locator('button') await button.click() await flushEvents() @@ -126,40 +127,41 @@ describe('rum errors', () => { // - Safari < 15 don't report the property disposition // - Firefox < 99 don't report csp violation at all // TODO: Remove this condition when upgrading to Safari 15 and Firefox 99 (see: https://datadoghq.atlassian.net/browse/RUM-1063) - if (!((getBrowserName() === 'safari' && getPlatformName() === 'macos') || getBrowserName() === 'firefox')) { - createTest('send CSP violation errors') - .withRum() - .withBody( - createBody(` + createTest('send CSP violation errors') + .withRum() + .withBody( + createBody(` const script = document.createElement('script'); script.src = "https://example.com/foo.js" document.body.appendChild(script) `) - ) - .run(async ({ intakeRegistry, baseUrl }) => { - const button = await $('button') - await button.click() - - await flushEvents() - - expect(intakeRegistry.rumErrorEvents.length).toBe(1) - expectError(intakeRegistry.rumErrorEvents[0].error, { - message: /^csp_violation: 'https:\/\/example\.com\/foo\.js' blocked by 'script-src(-elem)?' directive$/, - source: 'report', - stack: [ - /^script-src(-elem)?: 'https:\/\/example\.com\/foo\.js' blocked by 'script-src(-elem)?' directive of the policy/, - ` at @ ${baseUrl}/:`, - ], - handling: 'unhandled', - csp: { - disposition: 'enforce', - }, - }) - await withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) - }) + ) + .run(async ({ page, browserName, intakeRegistry, baseUrl, flushEvents, withBrowserLogs }) => { + const userAgent = await page.evaluate(() => navigator.userAgent) + test.skip(browserName === 'firefox' || (browserName === 'webkit' && userAgent.includes('Mac OS X'))) + + const button = await page.locator('button') + await button.click() + + await flushEvents() + + expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expectError(intakeRegistry.rumErrorEvents[0].error, { + message: /^csp_violation: 'https:\/\/example\.com\/foo\.js' blocked by 'script-src(-elem)?' directive$/, + source: 'report', + stack: [ + /^script-src(-elem)?: 'https:\/\/example\.com\/foo\.js' blocked by 'script-src(-elem)?' directive of the policy/, + ` at @ ${baseUrl}/:`, + ], + handling: 'unhandled', + csp: { + disposition: 'enforce', + }, }) - } + await withBrowserLogs((browserLogs) => { + expect(browserLogs.length).toEqual(1) + }) + }) }) function expectError( From 9a0ff8794fa42291e113faf9ba7a3b343a6e5273 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 24 Jan 2025 17:21:23 +0100 Subject: [PATCH 17/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20resources=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 2 +- test/e2e/scenario/rum/resources.scenario.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 095582075a..2d73090360 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -57,7 +57,7 @@ interface TestContext { flushBrowserLogs: () => void flushEvents: () => Promise deleteAllCookies: () => Promise - sendXhr: (url: string, headers: string[][]) => Promise + sendXhr: (url: string, headers?: string[][]) => Promise } type TestRunner = (testContext: TestContext) => Promise | void diff --git a/test/e2e/scenario/rum/resources.scenario.ts b/test/e2e/scenario/rum/resources.scenario.ts index 97c0d0fc1a..e139cd060c 100644 --- a/test/e2e/scenario/rum/resources.scenario.ts +++ b/test/e2e/scenario/rum/resources.scenario.ts @@ -249,7 +249,7 @@ test.describe('rum resources', () => { ) test.skip( - browserName === 'chromium', + browserName !== 'chromium', 'Only Chromium based browsers will emit predictable timings events for concurrent fetches' ) From 7a83019481de0fec9d5d5d3ebc671a75931c14ff Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 24 Jan 2025 17:22:46 +0100 Subject: [PATCH 18/68] =?UTF-8?q?=E2=9C=85=20add=20tracing=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + test/e2e/scenario/rum/tracing.scenario.ts | 73 +++++++++++++---------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index c2e51938f9..d7ad1f007b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -27,6 +27,7 @@ export default defineConfig({ '**/trackingConsent.scenario.ts', '**/telemetry.scenario.ts', '**/resources.scenario.ts', + '**/tracing.scenario.ts', '**/recorder.scenario.ts', '**/errors.scenario.ts', ], diff --git a/test/e2e/scenario/rum/tracing.scenario.ts b/test/e2e/scenario/rum/tracing.scenario.ts index f980b63e92..3c0d4fa985 100644 --- a/test/e2e/scenario/rum/tracing.scenario.ts +++ b/test/e2e/scenario/rum/tracing.scenario.ts @@ -1,11 +1,11 @@ +import { test, expect } from '@playwright/test' import type { IntakeRegistry } from '../../lib/framework' -import { flushEvents, createTest } from '../../lib/framework' -import { sendXhr } from '../../lib/helpers/browser' +import { createTest } from '../../lib/framework' -describe('tracing', () => { +test.describe('tracing', () => { createTest('trace xhr') .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, sendXhr, flushEvents }) => { const rawHeaders = await sendXhr('/headers', [ ['x-foo', 'bar'], ['x-foo', 'baz'], @@ -19,19 +19,22 @@ describe('tracing', () => { createTest('trace fetch') .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) - .run(async ({ intakeRegistry }) => { - const rawHeaders = await browser.executeAsync((done) => { - window - .fetch('/headers', { - headers: [ - ['x-foo', 'bar'], - ['x-foo', 'baz'], - ], + .run(async ({ intakeRegistry, flushEvents, page }) => { + const rawHeaders = await page.evaluate( + () => + new Promise((resolve) => { + window + .fetch('/headers', { + headers: [ + ['x-foo', 'bar'], + ['x-foo', 'baz'], + ], + }) + .then((response) => response.text()) + .then(resolve) + .catch(() => resolve(new Error('Fetch request failed!'))) }) - .then((response) => response.text()) - .then(done) - .catch(() => done(new Error('Fetch request failed!'))) - }) + ) const headers = parseHeaders(rawHeaders) checkRequestHeaders(headers) expect(headers['x-foo']).toBe('bar, baz') @@ -41,14 +44,17 @@ describe('tracing', () => { createTest('trace fetch with Request argument') .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) - .run(async ({ intakeRegistry }) => { - const rawHeaders = await browser.executeAsync((done) => { - window - .fetch(new Request('/headers', { headers: { 'x-foo': 'bar, baz' } })) - .then((response) => response.text()) - .then(done) - .catch(() => done(new Error('Fetch request failed!'))) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + const rawHeaders = await page.evaluate( + () => + new Promise((resolve) => { + window + .fetch(new Request('/headers', { headers: { 'x-foo': 'bar, baz' } })) + .then((response) => response.text()) + .then(resolve) + .catch(() => resolve(new Error('Fetch request failed!'))) + }) + ) const headers = parseHeaders(rawHeaders) checkRequestHeaders(headers) expect(headers['x-foo']).toBe('bar, baz') @@ -58,14 +64,17 @@ describe('tracing', () => { createTest('trace single argument fetch') .withRum({ service: 'service', allowedTracingUrls: ['LOCATION_ORIGIN'] }) - .run(async ({ intakeRegistry }) => { - const rawHeaders = await browser.executeAsync((done) => { - window - .fetch('/headers') - .then((response) => response.text()) - .then(done) - .catch(() => done(new Error('Fetch request failed!'))) - }) + .run(async ({ intakeRegistry, flushEvents, page }) => { + const rawHeaders = await page.evaluate( + () => + new Promise((resolve) => { + window + .fetch('/headers') + .then((response) => response.text()) + .then(resolve) + .catch(() => resolve(new Error('Fetch request failed!'))) + }) + ) const headers = parseHeaders(rawHeaders) checkRequestHeaders(headers) await flushEvents() From f1d83bbd2ac0cd024bff8078c06d276880558707 Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Fri, 24 Jan 2025 16:09:39 +0000 Subject: [PATCH 19/68] Migrate shadowDom.scenario.ts to Playwright --- packages/core/test/getCurrentJasmineSpec.ts | 21 +++++---- packages/core/test/registerCleanupTask.ts | 11 +++-- playwright.config.ts | 1 + .../scenario/recorder/shadowDom.scenario.ts | 45 ++++++++----------- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/packages/core/test/getCurrentJasmineSpec.ts b/packages/core/test/getCurrentJasmineSpec.ts index 59a114abe3..eb2de3fa99 100644 --- a/packages/core/test/getCurrentJasmineSpec.ts +++ b/packages/core/test/getCurrentJasmineSpec.ts @@ -1,14 +1,19 @@ let currentSpec: jasmine.SpecResult | null = null export function getCurrentJasmineSpec() { + if (typeof globalThis['jasmine'] !== 'object') { + throw new Error('Not inside a Jasmine test') + } return currentSpec } -jasmine.getEnv().addReporter({ - specStarted(specResult) { - currentSpec = specResult - }, - specDone() { - currentSpec = null - }, -}) +if (typeof globalThis['jasmine'] === 'object') { + globalThis['jasmine'].getEnv().addReporter({ + specStarted(specResult) { + currentSpec = specResult + }, + specDone() { + currentSpec = null + }, + }) +} diff --git a/packages/core/test/registerCleanupTask.ts b/packages/core/test/registerCleanupTask.ts index fcc3ad459a..af9edf9360 100644 --- a/packages/core/test/registerCleanupTask.ts +++ b/packages/core/test/registerCleanupTask.ts @@ -1,9 +1,14 @@ const cleanupTasks: Array<() => void> = [] export function registerCleanupTask(task: () => void) { + if (typeof globalThis['afterEach'] !== 'function') { + throw new Error('Not inside a Jasmine test') + } cleanupTasks.unshift(task) } -afterEach(() => { - cleanupTasks.splice(0).forEach((task) => task()) -}) +if (typeof globalThis['afterEach'] === 'function') { + afterEach(() => { + cleanupTasks.splice(0).forEach((task) => task()) + }) +} diff --git a/playwright.config.ts b/playwright.config.ts index d7ad1f007b..96f8f49576 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -20,6 +20,7 @@ export default defineConfig({ '**/logs.scenario.ts', '**/microfrontend.scenario.ts', '**/sessionStore.scenario.ts', + '**/shadowDom.scenario.ts', '**/transport.scenario.ts', '**/views.scenario.ts', '**/vitals.scenario.ts', diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index 52512988c8..9ed4fd87ad 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -18,7 +18,8 @@ import { findTextNode, } from '@datadog/browser-rum/test' -import { flushEvents, createTest, bundleSetup, html } from '../../lib/framework' +import { test, expect } from '@playwright/test' +import { createTest, bundleSetup, html } from '../../lib/framework' /** Will generate the following HTML * ```html @@ -156,7 +157,7 @@ class DivWithStyle extends HTMLElement { ` -describe('recorder with shadow DOM', () => { +test.describe('recorder with shadow DOM', () => { createTest('can record fullsnapshot with the detail inside the shadow root') .withRum({ defaultPrivacyLevel: 'allow' }) .withSetup(bundleSetup) @@ -164,7 +165,7 @@ describe('recorder with shadow DOM', () => { ${divShadowDom} `) - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -184,10 +185,10 @@ describe('recorder with shadow DOM', () => { ${divWithStyleShadowDom} `) - .run(async ({ intakeRegistry }) => { - if (!(await isAdoptedStyleSheetsSupported())) { - return pending('adoptedStyleSheets is not supported in this browser') - } + .run(async ({ flushEvents, intakeRegistry, page }) => { + const isAdoptedStyleSheetsSupported = await page.evaluate(() => document.adoptedStyleSheets !== undefined) + test.skip(!isAdoptedStyleSheetsSupported, 'adoptedStyleSheets is not supported in this browser') + await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -210,7 +211,7 @@ describe('recorder with shadow DOM', () => {
    `) - .run(async ({ intakeRegistry }) => { + .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -223,7 +224,7 @@ describe('recorder with shadow DOM', () => { shadowRoot: outsideShadowRoot, textContent: outsideTextContent, } = findElementsInShadowDom(fullSnapshot.data.node, 'privacy-set-outside') - expect(outsideShadowRoot?.isShadowRoot).toBeTrue() + expect(outsideShadowRoot?.isShadowRoot).toBe(true) expect(outsideInput?.attributes.value).toBe('***') expect(outsideTextContent).toBe('field privacy-set-outside: ') @@ -232,7 +233,7 @@ describe('recorder with shadow DOM', () => { shadowRoot: insideShadowRoot, textContent: insideTextContent, } = findElementsInShadowDom(fullSnapshot.data.node, 'privacy-set-inside') - expect(insideShadowRoot?.isShadowRoot).toBeTrue() + expect(insideShadowRoot?.isShadowRoot).toBe(true) expect(insideInput?.attributes.value).toBe('***') expect(insideTextContent).toBe('field privacy-set-inside: ') }) @@ -244,8 +245,8 @@ describe('recorder with shadow DOM', () => { ${divShadowDom} `) - .run(async ({ intakeRegistry }) => { - const div = await getNodeInsideShadowDom('my-div', 'div') + .run(async ({ flushEvents, intakeRegistry, page }) => { + const div = page.locator('my-div div') await div.click() await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) @@ -266,8 +267,8 @@ describe('recorder with shadow DOM', () => { ${divShadowDom} `) - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ flushEvents, intakeRegistry, page }) => { + await page.evaluate(() => { const host = document.body.querySelector('#host') as HTMLElement const div = host.shadowRoot!.querySelector('div') as HTMLElement div.innerText = 'titi' @@ -275,7 +276,8 @@ describe('recorder with shadow DOM', () => { await flushEvents() expect(intakeRegistry.replaySegments.length).toBe(1) const { validate, expectInitialNode, expectNewNode } = createMutationPayloadValidatorFromSegment( - intakeRegistry.replaySegments[0] + intakeRegistry.replaySegments[0], + { expect } ) validate({ adds: [ @@ -300,8 +302,8 @@ describe('recorder with shadow DOM', () => { ${scrollableDivShadowDom} `) - .run(async ({ intakeRegistry }) => { - const button = await getNodeInsideShadowDom('my-scrollable-div', 'button') + .run(async ({ flushEvents, intakeRegistry, page }) => { + const button = page.locator('my-scrollable-div button') // Triggering scrollTo from the test itself is not allowed // Thus, a callback to scroll the div was added to the button 'click' event @@ -337,12 +339,3 @@ function findElementsInShadowDom(node: SerializedNodeWithId, id: string) { expect(textContent).toBeTruthy() return { shadowHost, shadowRoot, input, text, textContent } } - -async function getNodeInsideShadowDom(hostTag: string, selector: string) { - const host = await $(hostTag) - return host.shadow$(selector) -} - -function isAdoptedStyleSheetsSupported(): Promise { - return browser.execute(() => document.adoptedStyleSheets !== undefined) -} From 4733f3343fd57b83b6328d2b4b546f5a916ed32b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 24 Jan 2025 18:54:05 +0100 Subject: [PATCH 20/68] =?UTF-8?q?=E2=9C=85=20recorder/viewports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + .../scenario/recorder/viewports.scenario.ts | 300 +++++++++--------- 2 files changed, 149 insertions(+), 152 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 96f8f49576..d92418d3ab 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ '**/tracing.scenario.ts', '**/recorder.scenario.ts', '**/errors.scenario.ts', + '**/viewports.scenario.ts', ], /* Run tests in files in parallel */ fullyParallel: true, diff --git a/test/e2e/scenario/recorder/viewports.scenario.ts b/test/e2e/scenario/recorder/viewports.scenario.ts index b6b3163d7d..74577cb928 100644 --- a/test/e2e/scenario/recorder/viewports.scenario.ts +++ b/test/e2e/scenario/recorder/viewports.scenario.ts @@ -1,10 +1,12 @@ import type { ViewportResizeData, ScrollData } from '@datadog/browser-rum/cjs/types' import { IncrementalSource } from '@datadog/browser-rum/cjs/types' -import { findAllIncrementalSnapshots, findAllVisualViewports } from '@datadog/browser-rum/test' +import { findAllIncrementalSnapshots, findAllVisualViewports } from '@datadog/browser-rum/test/segments' +import type { Page } from '@playwright/test' +import { test, expect } from '@playwright/test' +import { wait } from '@datadog/browser-core/test/wait' import type { IntakeRegistry } from '../../lib/framework' -import { flushEvents, createTest, bundleSetup, html } from '../../lib/framework' -import { getBrowserName, getPlatformName } from '../../lib/helpers/browser' +import { createTest, bundleSetup, html } from '../../lib/framework' const NAVBAR_HEIGHT_CHANGE_UPPER_BOUND = 30 const VIEWPORT_META_TAGS = ` @@ -15,39 +17,40 @@ const VIEWPORT_META_TAGS = ` > ` -describe('recorder', () => { - beforeEach(() => { - if (isGestureUnsupported()) { - pending('no touch gesture support') - } +test.describe('recorder', () => { + test.beforeEach(({ hasTouch, browserName }, testInfo) => { + testInfo.skip(!hasTouch, 'no touch gesture support') + testInfo.skip(browserName !== 'chromium', 'only chromium supports touch gestures emulation for now (via CDP)') }) - describe('layout viewport properties', () => { - createTest('getWindowWidth/Height should not be affected by pinch zoom') - .withRum() - .withSetup(bundleSetup) - .withBody(html`${VIEWPORT_META_TAGS}`) - .run(async ({ intakeRegistry }) => { - await buildScrollablePage() + test.describe('layout viewport properties', () => { + test.describe('', () => { + createTest('getWindowWidth/Height should not be affected by pinch zoom') + .withRum() + .withSetup(bundleSetup) + .withBody(html`${VIEWPORT_META_TAGS}`) + .run(async ({ intakeRegistry, page, flushEvents }) => { + await buildScrollablePage(page) - const { innerWidth, innerHeight } = await getWindowInnerDimensions() - await performSignificantZoom() + const { innerWidth, innerHeight } = await getWindowInnerDimensions(page) - await browser.execute(() => { - window.dispatchEvent(new Event('resize')) - }) + await performSignificantZoom(page) + + await page.evaluate(() => { + window.dispatchEvent(new Event('resize')) + }) - const lastViewportResizeData = ( - await getLastRecord(intakeRegistry, (segment) => + await flushEvents() + const lastViewportResizeData = getLastRecord(intakeRegistry, (segment) => findAllIncrementalSnapshots(segment, IncrementalSource.ViewportResize) - ) - ).data as ViewportResizeData + ).data as ViewportResizeData - const scrollbarThicknessCorrection = await getScrollbarThicknessCorrection() + const scrollbarThicknessCorrection = await getScrollbarThicknessCorrection(page) - expectToBeNearby(lastViewportResizeData.width, innerWidth - scrollbarThicknessCorrection) - expectToBeNearby(lastViewportResizeData.height, innerHeight - scrollbarThicknessCorrection) - }) + expectToBeNearby(lastViewportResizeData.width, innerWidth - scrollbarThicknessCorrection) + expectToBeNearby(lastViewportResizeData.height, innerHeight - scrollbarThicknessCorrection) + }) + }) /** * window.ScrollX/Y on some devices/browsers are changed by pinch zoom @@ -57,30 +60,29 @@ describe('recorder', () => { .withRum() .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, flushEvents, page }) => { const VISUAL_SCROLL_DOWN_PX = 60 const LAYOUT_SCROLL_AMOUNT = 20 - await buildScrollablePage() - await performSignificantZoom() - await resetWindowScroll() + await buildScrollablePage(page) + await performSignificantZoom(page) + await resetWindowScroll(page) - const initialVisualViewport = await getVisualViewport() - const { scrollX: initialScrollX, scrollY: initialScrollY } = await getWindowScroll() + const initialVisualViewport = await getVisualViewport(page) + const { scrollX: initialScrollX, scrollY: initialScrollY } = await getWindowScroll(page) // Add Visual Viewport Scroll - await visualScrollVerticallyDown(VISUAL_SCROLL_DOWN_PX) + await visualScrollVerticallyDown(page, VISUAL_SCROLL_DOWN_PX) // Add Layout Viewport Scroll - await layoutScrollTo(LAYOUT_SCROLL_AMOUNT, LAYOUT_SCROLL_AMOUNT) + await layoutScrollTo(page, LAYOUT_SCROLL_AMOUNT, LAYOUT_SCROLL_AMOUNT) - const nextVisualViewport = await getVisualViewport() - const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll() + const nextVisualViewport = await getVisualViewport(page) + const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll(page) - const lastScrollData = ( - await getLastRecord(intakeRegistry, (segment) => - findAllIncrementalSnapshots(segment, IncrementalSource.Scroll) - ) + await flushEvents() + const lastScrollData = getLastRecord(intakeRegistry, (segment) => + findAllIncrementalSnapshots(segment, IncrementalSource.Scroll) ).data as ScrollData // Height changes because URL address bar changes due to scrolling @@ -95,18 +97,19 @@ describe('recorder', () => { }) }) - describe('visual viewport properties', () => { + test.describe('visual viewport properties', () => { createTest('pinch zoom "scroll" event reports visual viewport position') .withRum() .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, page, flushEvents }) => { const VISUAL_SCROLL_DOWN_PX = 100 - await buildScrollablePage() - await performSignificantZoom() - await visualScrollVerticallyDown(VISUAL_SCROLL_DOWN_PX) - const nextVisualViewportDimension = await getVisualViewport() - const lastVisualViewportRecord = await getLastRecord(intakeRegistry, findAllVisualViewports) + await buildScrollablePage(page) + await performSignificantZoom(page) + await visualScrollVerticallyDown(page, VISUAL_SCROLL_DOWN_PX) + const nextVisualViewportDimension = await getVisualViewport(page) + await flushEvents() + const lastVisualViewportRecord = getLastRecord(intakeRegistry, findAllVisualViewports) expectToBeNearby(lastVisualViewportRecord.data.pageTop, nextVisualViewportDimension.pageTop) }) @@ -114,18 +117,16 @@ describe('recorder', () => { .withRum() .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) - .run(async ({ intakeRegistry }) => { - await performSignificantZoom() - const nextVisualViewportDimension = await getVisualViewport() - const lastVisualViewportRecord = await getLastRecord(intakeRegistry, findAllVisualViewports) + .run(async ({ intakeRegistry, page, flushEvents }) => { + await performSignificantZoom(page) + const nextVisualViewportDimension = await getVisualViewport(page) + await flushEvents() + const lastVisualViewportRecord = getLastRecord(intakeRegistry, findAllVisualViewports) expectToBeNearby(lastVisualViewportRecord.data.scale, nextVisualViewportDimension.scale) }) }) }) -const isGestureUnsupported = () => - /firefox|safari|edge/.test(getBrowserName()) || /windows|linux/.test(getPlatformName()) - // Flakiness: Working with viewport sizes has variations per device of a few pixels function expectToBeNearby(numA: number, numB: number) { const test = Math.abs(numA - numB) <= 5 @@ -135,54 +136,49 @@ function expectToBeNearby(numA: number, numB: number) { } } -async function pinchZoom(xChange: number) { +async function pinchZoom(page: Page, xChange: number) { // Cannot exceed the bounds of a device's screen, at start or end positions. // So pick a midpoint on small devices, roughly 180px. const xBase = 180 const yBase = 180 const xOffsetFingerTwo = 25 - // Scrolling too fast can show or hide the address bar on some device browsers. - const moveDurationMs = 400 const pauseDurationMs = 150 - const actions = [ - { - type: 'pointer', - id: 'finger1', - parameters: { pointerType: 'touch' }, - actions: [ - { type: 'pointerMove', duration: 0, x: xBase, y: yBase }, - { type: 'pointerDown', button: 0 }, - { type: 'pause', duration: pauseDurationMs }, - { type: 'pointerMove', duration: moveDurationMs, origin: 'pointer', x: -xChange, y: 0 }, - { type: 'pointerUp', button: 0 }, - ], - }, - { - type: 'pointer', - id: 'finger2', - parameters: { pointerType: 'touch' }, - actions: [ - { type: 'pointerMove', duration: 0, x: xBase + xOffsetFingerTwo, y: yBase }, - { type: 'pointerDown', button: 0 }, - { type: 'pause', duration: pauseDurationMs }, - { type: 'pointerMove', duration: moveDurationMs, origin: 'pointer', x: +xChange, y: 0 }, - { type: 'pointerUp', button: 0 }, - ], - }, - ] - await browser.performActions(actions) + + const cdp = await page.context().newCDPSession(page) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchStart', + touchPoints: [ + { x: xBase, y: yBase, id: 0 }, + { x: xBase + xOffsetFingerTwo, y: yBase, id: 1 }, + ], + }) + await wait(pauseDurationMs) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchMove', + touchPoints: [ + { x: xBase, y: yBase, id: 0 }, + { x: xBase + xChange, y: yBase, id: 1 }, + ], + }) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchEnd', + touchPoints: [ + { x: xBase, y: yBase, id: 0 }, + { x: xBase + xChange, y: yBase, id: 1 }, + ], + }) } -async function performSignificantZoom() { - const initialVisualViewport = await getVisualViewport() - await pinchZoom(150) - await pinchZoom(150) - const nextVisualViewport = await getVisualViewport() +async function performSignificantZoom(page: Page) { + const initialVisualViewport = await getVisualViewport(page) + await pinchZoom(page, 150) + await pinchZoom(page, 150) + const nextVisualViewport = await getVisualViewport(page) // Test the test: ensure pinch zoom was applied expect(initialVisualViewport.scale < nextVisualViewport.scale).toBeTruthy() } -async function visualScrollVerticallyDown(yChange: number) { +async function visualScrollVerticallyDown(page: Page, yChange: number) { // Providing a negative offset value will scroll up. // NOTE: Some devices may invert scroll direction // Cannot exceed the bounds of a device's screen, at start or end positions. @@ -190,28 +186,26 @@ async function visualScrollVerticallyDown(yChange: number) { const xBase = 180 const yBase = 180 // Scrolling too fast can show or hide the address bar on some device browsers. - const moveDurationMs = 800 const pauseDurationMs = 150 - const actions = [ - { - type: 'pointer', - id: 'finger1', - parameters: { pointerType: 'touch' }, - actions: [ - { type: 'pointerMove', duration: 0, x: xBase, y: yBase }, - { type: 'pointerDown', button: 0 }, - { type: 'pause', duration: pauseDurationMs }, - { type: 'pointerMove', duration: moveDurationMs, origin: 'pointer', x: 0, y: -yChange }, - { type: 'pointerUp', button: 0 }, - ], - }, - ] - await browser.performActions(actions) + const cdp = await page.context().newCDPSession(page) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchStart', + touchPoints: [{ x: xBase, y: yBase, id: 0 }], + }) + await wait(pauseDurationMs) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchMove', + touchPoints: [{ x: xBase, y: yBase - yChange, id: 0 }], + }) + await cdp.send('Input.dispatchTouchEvent', { + type: 'touchEnd', + touchPoints: [{ x: xBase, y: yBase - yChange, id: 0 }], + }) } -async function buildScrollablePage() { - await browser.execute(() => { +async function buildScrollablePage(page: Page) { + await page.evaluate(() => { document.documentElement.style.setProperty('width', '5000px') document.documentElement.style.setProperty('height', '5000px') document.documentElement.style.setProperty('margin', '0px') @@ -233,8 +227,8 @@ interface VisualViewportData { pageTop: number } -function getVisualViewport(): Promise { - return browser.execute(() => { +function getVisualViewport(page: Page): Promise { + return page.evaluate(() => { const visual = window.visualViewport || ({} as Record) return { scale: visual.scale, @@ -248,76 +242,78 @@ function getVisualViewport(): Promise { }) as Promise } -function getWindowScroll() { - return browser.execute(() => ({ +function getWindowScroll(page: Page) { + return page.evaluate(() => ({ scrollX: window.scrollX, scrollY: window.scrollY, })) as Promise<{ scrollX: number; scrollY: number }> } -function getScrollbarThickness(): Promise { - // https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript#answer-13382873 - return browser.execute(() => { - // Creating invisible container - const outer = document.createElement('div') - outer.style.visibility = 'hidden' - outer.style.overflow = 'scroll' // forcing scrollbar to appear - ;(outer.style as any).msOverflowStyle = 'scrollbar' // needed for WinJS apps - document.body.appendChild(outer) - // Creating inner element and placing it in the container - const inner = document.createElement('div') - outer.appendChild(inner) - // Calculating difference between container's full width and the child width - const scrollbarThickness = outer.offsetWidth - inner.offsetWidth - // Removing temporary elements from the DOM - document.body.removeChild(outer) - return scrollbarThickness - }) -} +// TODO(playwright migration): I'm not sure if this is still needed? +// function getScrollbarThickness(page: Page): Promise { +// // https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript#answer-13382873 +// return page.evaluate(() => { +// // Creating invisible container +// const outer = document.createElement('div') +// outer.style.visibility = 'hidden' +// outer.style.overflow = 'scroll' // forcing scrollbar to appear +// ;(outer.style as any).msOverflowStyle = 'scrollbar' // needed for WinJS apps +// document.body.appendChild(outer) +// // Creating inner element and placing it in the container +// const inner = document.createElement('div') +// outer.appendChild(inner) +// // Calculating difference between container's full width and the child width +// const scrollbarThickness = outer.offsetWidth - inner.offsetWidth +// // Removing temporary elements from the DOM +// document.body.removeChild(outer) +// return scrollbarThickness +// }) +// } // Mac OS X Chrome scrollbars are included here (~15px) which seems to be against spec // Scrollbar edge-case handling not considered right now, further investigation needed -async function getScrollbarThicknessCorrection(): Promise { - let scrollbarThickness = 0 - if (getBrowserName() === 'chrome' && getPlatformName() === 'macos') { - scrollbarThickness = await getScrollbarThickness() - } +function getScrollbarThicknessCorrection(_page: Page): number { + const scrollbarThickness = 0 + + // TODO(playwright migration): I'm not sure if this is still needed? + // if (getBrowserName() === 'chrome' && getPlatformName() === 'macos') { + // scrollbarThickness = await getScrollbarThickness(page) + // } + return scrollbarThickness } -async function getLastRecord(intakeRegistry: IntakeRegistry, filterMethod: (segment: any) => T[]): Promise { - await flushEvents() +function getLastRecord(intakeRegistry: IntakeRegistry, filterMethod: (segment: any) => T[]): T { const segment = intakeRegistry.replaySegments.at(-1) const foundRecords = filterMethod(segment) return foundRecords[foundRecords.length - 1] } -function getWindowInnerDimensions() { - return browser.execute(() => ({ +function getWindowInnerDimensions(page: Page) { + return page.evaluate(() => ({ innerWidth: window.innerWidth, innerHeight: window.innerHeight, })) as Promise<{ innerWidth: number; innerHeight: number }> } -async function resetWindowScroll() { - await browser.execute(() => { +async function resetWindowScroll(page: Page) { + await page.evaluate(() => { window.scrollTo(-500, -500) }) - const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll() + const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll(page) // Ensure our methods are applied correctly expect(nextScrollX).toBe(0) expect(nextScrollY).toBe(0) } -async function layoutScrollTo(scrollX: number, scrollY: number) { - await browser.execute( - (x, y) => { - window.scrollTo(x, y) +async function layoutScrollTo(page: Page, scrollX: number, scrollY: number) { + await page.evaluate( + ({ scrollX, scrollY }) => { + window.scrollTo(scrollX, scrollY) }, - scrollX, - scrollY + { scrollX, scrollY } ) - const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll() + const { scrollX: nextScrollX, scrollY: nextScrollY } = await getWindowScroll(page) // Ensure our methods are applied correctly expect(scrollX).toBe(nextScrollX) expect(scrollY).toBe(nextScrollY) From 49deab2f74243816b04ec27d6f18d36a30763d1e Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 10:26:47 +0100 Subject: [PATCH 21/68] =?UTF-8?q?=E2=9C=85=20add=20developer-extension=20s?= =?UTF-8?q?cenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playwright.config.ts | 1 + .../developerExtension.scenario.ts | 66 ++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index d92418d3ab..8b995aa1cf 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ '**/tracing.scenario.ts', '**/recorder.scenario.ts', '**/errors.scenario.ts', + '**/developerExtension.scenario.ts', '**/viewports.scenario.ts', ], /* Run tests in files in parallel */ diff --git a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts index 91af823c89..9af5076501 100644 --- a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts +++ b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts @@ -1,30 +1,52 @@ -describe('developer-extension', () => { - it('should switch between tabs', async () => { - const panel = new DeveloperExtensionPanel() - await panel.open() - expect(await panel.getSelectedTab()).toEqual('Events') - - await panel.getTab('Infos').click() - expect(await panel.getSelectedTab()).toEqual('Infos') +import { test as base, chromium, Page, type BrowserContext, expect } from '@playwright/test' +import path from 'path' + +const test = base.extend<{ + context: BrowserContext + extensionId: string + developerExtension: DeveloperExtensionPage +}>({ + context: async ({}, use) => { + const pathToExtension = path.join(process.cwd(), 'developer-extension', 'dist') + + const context = await chromium.launchPersistentContext('', { + channel: 'chromium', + args: [`--disable-extensions-except=${pathToExtension}`, `--load-extension=${pathToExtension}`], + }) + await use(context) + await context.close() + }, + extensionId: async ({ context }, use) => { + let [background] = context.serviceWorkers() + if (!background) background = await context.waitForEvent('serviceworker') + + const extensionId = background.url().split('/')[2] + await use(extensionId) + }, + developerExtension: async ({ page, extensionId }, use) => { + await page.goto(`chrome-extension://${extensionId}/panel.html`) + + await use(new DeveloperExtensionPage(page)) + }, +}) + +test.describe('developer-extension', () => { + test('should switch between tabs', async ({ developerExtension: page }) => { + expect(await page.getSelectedTab().innerText()).toEqual('Events') + + await page.getTab('Infos').click() + expect(await page.getSelectedTab().innerText()).toEqual('Infos') }) }) -class DeveloperExtensionPanel { - async open() { - await browser.url('chrome://extensions') - // extensions page is built with custom elements, >>> selector to traverse shadow DOM - // cf https://webdriver.io/docs/selectors/#deep-selectors - const extensionId = await $('>>>extensions-item').getAttribute('id') - const url = `chrome-extension://${extensionId}/panel.html` - await browser.url(url) - expect(await browser.getUrl()).toEqual(url) - } +class DeveloperExtensionPage { + constructor(public readonly page: Page) {} - getSelectedTab() { - return $("button[role='tab'][aria-selected='true']").getText() + getTab(name: string) { + return this.page.getByRole('tab', { name }) } - getTab(content: string) { - return $(`button[role='tab']=${content}`) + getSelectedTab() { + return this.page.getByRole('tab', { selected: true }) } } From e424683e56e5e5f7c5b1f3c1788dd1407f3b9896 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 13:30:24 +0100 Subject: [PATCH 22/68] =?UTF-8?q?=F0=9F=91=B7=20fix=20e2e=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- playwright.config.ts | 100 ---------------------------- test/e2e/notice-reporter.ts | 14 ++++ test/e2e/playwright.base.config.ts | 35 ++++++++++ test/e2e/playwright.local.config.ts | 12 ++++ 5 files changed, 62 insertions(+), 101 deletions(-) delete mode 100644 playwright.config.ts create mode 100644 test/e2e/notice-reporter.ts create mode 100644 test/e2e/playwright.base.config.ts create mode 100644 test/e2e/playwright.local.config.ts diff --git a/package.json b/package.json index a2cf39d05f..2e1e235d18 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "test:script": "node --test --experimental-test-module-mocks './scripts/**/*.spec.*'", "test:unit:watch": "yarn test:unit --no-single-run", "test:unit:bs": "node ./scripts/test/bs-wrapper.js karma start test/unit/karma.bs.conf.js", - "test:e2e": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && wdio test/e2e/wdio.local.conf.ts", + "test:e2e": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && playwright test --config test/e2e/playwright.local.config.ts", "test:e2e:bs": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && node ./scripts/test/bs-wrapper.js wdio test/e2e/wdio.bs.conf.ts", "test:e2e:developer-extension": "yarn build && wdio test/e2e/wdio.developer-extension.conf.ts", "test:compat:tsc": "scripts/cli check_typescript_compatibility", diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 8b995aa1cf..0000000000 --- a/playwright.config.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { defineConfig, devices } from '@playwright/test' - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// import dotenv from 'dotenv'; -// import path from 'path'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: './test/e2e/scenario', - testMatch: [ - '**/actions.scenario.ts', - '**/eventBridge.scenario.ts', - '**/init.scenario.ts', - '**/logs.scenario.ts', - '**/microfrontend.scenario.ts', - '**/sessionStore.scenario.ts', - '**/shadowDom.scenario.ts', - '**/transport.scenario.ts', - '**/views.scenario.ts', - '**/vitals.scenario.ts', - '**/s8sInject.scenario.ts', - '**/trackingConsent.scenario.ts', - '**/telemetry.scenario.ts', - '**/resources.scenario.ts', - '**/tracing.scenario.ts', - '**/recorder.scenario.ts', - '**/errors.scenario.ts', - '**/developerExtension.scenario.ts', - '**/viewports.scenario.ts', - ], - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, -}) diff --git a/test/e2e/notice-reporter.ts b/test/e2e/notice-reporter.ts new file mode 100644 index 0000000000..ff6dc1a4dd --- /dev/null +++ b/test/e2e/notice-reporter.ts @@ -0,0 +1,14 @@ +import type { Reporter } from '@playwright/test/reporter' +import { APPLICATION_ID } from 'lib/helpers/configuration' +import { getRunId } from '../envUtils' + +export default class NoticeReporter implements Reporter { + onBegin() { + console.log( + `[RUM events] https://app.datadoghq.com/rum/explorer?query=${encodeURIComponent( + `@application.id:${APPLICATION_ID} @context.run_id:"${getRunId()}"` + )}` + ) + console.log(`[Log events] https://app.datadoghq.com/logs?query=${encodeURIComponent(`@run_id:"${getRunId()}"`)}\n`) + } +} diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts new file mode 100644 index 0000000000..d4a96028b0 --- /dev/null +++ b/test/e2e/playwright.base.config.ts @@ -0,0 +1,35 @@ +import { ReporterDescription, Config } from '@playwright/test' +import { getTestReportDirectory } from '../envUtils' + +const reporters: ReporterDescription[] = [['line'], ['./notice-reporter.ts']] + +const testReportDirectory = getTestReportDirectory() +if (testReportDirectory) { + reporters.push([ + 'junit', + { + outputFile: `${testReportDirectory}/results.xml`, + }, + ]) +} else { + reporters.push(['html']) +} + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export const config: Config = { + testDir: './scenario', + testMatch: ['**/*.scenario.ts'], + testIgnore: ['developer-extension/*.scenario.ts', '**/sessions.scenario.ts'], + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 20, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: reporters, + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + trace: 'on-first-retry', + }, +} diff --git a/test/e2e/playwright.local.config.ts b/test/e2e/playwright.local.config.ts new file mode 100644 index 0000000000..04fa8d6d51 --- /dev/null +++ b/test/e2e/playwright.local.config.ts @@ -0,0 +1,12 @@ +import { config as baseConfig } from './playwright.base.config' +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + ...baseConfig, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) From 35ab323071a616b3c75bbeb0e423dd39dd369841 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 13:35:24 +0100 Subject: [PATCH 23/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20missing=20test.descr?= =?UTF-8?q?ibe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 2d73090360..161799aabe 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -177,7 +177,7 @@ function declareTestsForSetups( runner: TestRunner ) { if (setups.length > 1) { - describe(title, () => { + test.describe(title, () => { for (const { name, factory } of setups) { declareTest(name!, setupOptions, factory, runner) } From b73395ce6d586354c74a738f1a4813fe02bb7ff7 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 13:44:05 +0100 Subject: [PATCH 24/68] =?UTF-8?q?=F0=9F=91=8C=20install=20browser=20depend?= =?UTF-8?q?encies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 4 +++- test/e2e/playwright.base.config.ts | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d61921db5..fbba8ebdc8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -219,11 +219,13 @@ e2e: interruptible: true artifacts: when: always - paths: ['test-report/e2e/specs.log'] + paths: + - 'test-report/e2e/*' reports: junit: test-report/e2e/*.xml script: - yarn + - yarn playwright install --with-deps - FORCE_COLOR=1 yarn test:e2e after_script: - node ./scripts/test/export-test-result.js e2e diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index d4a96028b0..527e4cfcf6 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -8,7 +8,7 @@ if (testReportDirectory) { reporters.push([ 'junit', { - outputFile: `${testReportDirectory}/results.xml`, + outputFile: `../../${testReportDirectory}/results.xml`, }, ]) } else { @@ -26,9 +26,7 @@ export const config: Config = { forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: 20, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: reporters, - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { trace: 'on-first-retry', }, From 5d0f2efad17e7aed62a75eb0cba8a785c5cb504e Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 15:21:29 +0100 Subject: [PATCH 25/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20s8s=20and=20dev-exte?= =?UTF-8?q?nsion=20e2e=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 9 --------- test/e2e/playwright.base.config.ts | 2 +- test/e2e/scenario/rum/s8sInject.scenario.ts | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fbba8ebdc8..937b54bcf3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -230,15 +230,6 @@ e2e: after_script: - node ./scripts/test/export-test-result.js e2e -e2e-developer-extension: - extends: - - .base-configuration - - .test-allowed-branches - interruptible: true - script: - - yarn - - yarn test:e2e:developer-extension - check-licenses: extends: - .base-configuration diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 527e4cfcf6..9c4a2c2164 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -21,7 +21,7 @@ if (testReportDirectory) { export const config: Config = { testDir: './scenario', testMatch: ['**/*.scenario.ts'], - testIgnore: ['developer-extension/*.scenario.ts', '**/sessions.scenario.ts'], + testIgnore: ['**/sessions.scenario.ts'], fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, diff --git a/test/e2e/scenario/rum/s8sInject.scenario.ts b/test/e2e/scenario/rum/s8sInject.scenario.ts index 5570316034..baa878de87 100644 --- a/test/e2e/scenario/rum/s8sInject.scenario.ts +++ b/test/e2e/scenario/rum/s8sInject.scenario.ts @@ -14,7 +14,7 @@ test.describe('Inject RUM with Puppeteer', () => { async function injectRumWithPuppeteer() { const ddRUM = fs.readFileSync(RUM_BUNDLE, 'utf8') - const puppeteerBrowser = await puppeteer.launch({ headless: false, devtools: true }) + const puppeteerBrowser = await puppeteer.launch({ headless: true, devtools: true, args: ['--no-sandbox'] }) let injected = true const page = await puppeteerBrowser.newPage() From 772ee1013f2ea788408b72a9504d9db21afe30f1 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 Jan 2025 15:36:38 +0100 Subject: [PATCH 26/68] =?UTF-8?q?=F0=9F=91=B7=20update=20LICENSE-3rdparty.?= =?UTF-8?q?csv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE-3rdparty.csv | 7 +- package.json | 8 - yarn.lock | 1225 +----------------------------------------- 3 files changed, 30 insertions(+), 1210 deletions(-) diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 20ecee5b51..fb52fa481d 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -12,6 +12,7 @@ prod,react,MIT,Copyright (c) Facebook, Inc. and its affiliates. prod,react-dom,MIT,Copyright (c) Facebook, Inc. and its affiliates. dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, dev,@jsdevtools/coverage-istanbul-loader,MIT,Copyright (c) 2015 James Messinger +dev,@playwright/test,Apache-2.0,Copyright Microsoft Corporation dev,@types/chrome,MIT,Copyright Microsoft Corporation dev,@types/connect-busboy,MIT,Copyright Microsoft Corporation dev,@types/cors,MIT,Copyright Microsoft Corporation @@ -22,12 +23,6 @@ dev,@types/node-forge,MIT,Copyright Microsoft Corporation dev,@types/pako,MIT,Copyright Microsoft Corporation dev,@types/react,MIT,Copyright Microsoft Corporation dev,@types/react-dom,MIT,Copyright Microsoft Corporation -dev,@wdio/browserstack-service,MIT,Copyright JS Foundation and other contributors -dev,@wdio/cli,MIT,Copyright JS Foundation and other contributors -dev,@wdio/jasmine-framework,MIT,Copyright JS Foundation and other contributors -dev,@wdio/junit-reporter,MIT,Copyright (c) OpenJS Foundation and other contributors -dev,@wdio/local-runner,MIT,Copyright JS Foundation and other contributors -dev,@wdio/spec-reporter,MIT,Copyright JS Foundation and other contributors dev,@webextension-toolbox/webpack-webextension-plugin,MIT,Copyright 2018 Henrik Wenz (handtrix@gmail.com) dev,ajv,MIT,Copyright 2015-2017 Evgeny Poberezkin dev,browserstack-local,MIT,Copyright 2016 BrowserStack diff --git a/package.json b/package.json index 2e1e235d18..9b157bab57 100644 --- a/package.json +++ b/package.json @@ -40,14 +40,6 @@ "@types/express": "4.17.21", "@types/jasmine": "3.10.18", "@types/node": "22.10.2", - "@typescript-eslint/eslint-plugin": "8.16.0", - "@typescript-eslint/parser": "8.16.0", - "@wdio/browserstack-service": "8.41.0", - "@wdio/cli": "8.41.0", - "@wdio/jasmine-framework": "8.41.0", - "@wdio/junit-reporter": "8.41.0", - "@wdio/local-runner": "8.41.0", - "@wdio/spec-reporter": "8.41.0", "ajv": "6.12.6", "browserstack-local": "1.5.6", "chrome-webstore-upload": "3.1.4", diff --git a/yarn.lock b/yarn.lock index 8dcc6566c7..9b7a283db1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,7 +24,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.14.5, @babel/code-frame@npm:^7.21.4": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.14.5": version: 7.22.10 resolution: "@babel/code-frame@npm:7.22.10" dependencies: @@ -291,16 +291,6 @@ __metadata: languageName: node linkType: hard -"@browserstack/ai-sdk-node@npm:1.5.9": - version: 1.5.9 - resolution: "@browserstack/ai-sdk-node@npm:1.5.9" - dependencies: - axios: "npm:^1.7.4" - uuid: "npm:9.0.1" - checksum: 10c0/71f3c13d6d2712e676a2f08d9a671cd9adf8be9cb50e1e4b4f9257acc1330735792f155e73fa24cbb16fe6ec9faf0c35f43fe454753950d42bb3f319089041c1 - languageName: node - linkType: hard - "@colors/colors@npm:1.5.0": version: 1.5.0 resolution: "@colors/colors@npm:1.5.0" @@ -692,15 +682,6 @@ __metadata: languageName: node linkType: hard -"@jest/expect-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/expect-utils@npm:29.7.0" - dependencies: - jest-get-type: "npm:^29.6.3" - checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a - languageName: node - linkType: hard - "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -710,20 +691,6 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/types@npm:29.6.3" - dependencies: - "@jest/schemas": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 - languageName: node - linkType: hard - "@jridgewell/gen-mapping@npm:^0.3.0": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -759,7 +726,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": version: 1.5.0 resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 @@ -885,15 +852,6 @@ __metadata: languageName: node linkType: hard -"@ljharb/through@npm:^2.3.11": - version: 2.3.12 - resolution: "@ljharb/through@npm:2.3.12" - dependencies: - call-bind: "npm:^1.0.5" - checksum: 10c0/7560aaef7b6ef88c16783ffde37278e2177c7f0f5427400059a8a7687b144dc711bf5b2347ab27e858a29f25e4b868d77c915c9614bc399b82b8123430614653 - languageName: node - linkType: hard - "@mantine/core@npm:7.16.1": version: 7.16.1 resolution: "@mantine/core@npm:7.16.1" @@ -1425,40 +1383,6 @@ __metadata: languageName: node linkType: hard -"@open-draft/until@npm:^1.0.3": - version: 1.0.3 - resolution: "@open-draft/until@npm:1.0.3" - checksum: 10c0/f88bcd774b55359d14a4fa80f7bfe7d9d6d26a5995e94e823e43b211656daae3663e983f0a996937da286d22f6f5da2087b661845302f236ba27f8529dcd14fb - languageName: node - linkType: hard - -"@percy/appium-app@npm:^2.0.1": - version: 2.0.3 - resolution: "@percy/appium-app@npm:2.0.3" - dependencies: - "@percy/sdk-utils": "npm:^1.27.0-beta.0" - tmp: "npm:^0.2.1" - checksum: 10c0/d7a8211f2cf19c5efa8b1414b072f51e334fabdfa8e5fcc686df11d632d022c14b98338f6b4a5cac05e5e8f9d1dd90920cc3e143833186ba7ac99addd877fee2 - languageName: node - linkType: hard - -"@percy/sdk-utils@npm:^1.27.0-beta.0, @percy/sdk-utils@npm:^1.27.2": - version: 1.27.7 - resolution: "@percy/sdk-utils@npm:1.27.7" - checksum: 10c0/35100ab397f9bb0e0d41b4105c5e0f60975cb4b1152fe58780c3aa860a7806d0c629d162be02d7caf07588f5643158fd638d1684fcb5bc612c69607fe4d6bdec - languageName: node - linkType: hard - -"@percy/selenium-webdriver@npm:^2.0.3": - version: 2.0.3 - resolution: "@percy/selenium-webdriver@npm:2.0.3" - dependencies: - "@percy/sdk-utils": "npm:^1.27.2" - node-request-interceptor: "npm:^0.6.3" - checksum: 10c0/3d2e3e7fba2606a6df172d8e139e0db488cd2edbe70fc883efa909ceca7519ef8d6870acac7c8bb2030e0f0586ead4b4fe529a2fecc6bb3114d1f663bdce1636 - languageName: node - linkType: hard - "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -1863,13 +1787,6 @@ __metadata: languageName: node linkType: hard -"@types/gitconfiglocal@npm:^2.0.1": - version: 2.0.1 - resolution: "@types/gitconfiglocal@npm:2.0.1" - checksum: 10c0/605317d889307b3512d1f2266ee47ff90d8d5ecbe95f02d8a2705fe8bdbf17b8cd680cc1f38791e87ba0685e796df1df47efad7460f88240814dd51146c8bd08 - languageName: node - linkType: hard - "@types/glob@npm:^7.1.3": version: 7.2.0 resolution: "@types/glob@npm:7.2.0" @@ -1901,31 +1818,6 @@ __metadata: languageName: node linkType: hard -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0": - version: 2.0.1 - resolution: "@types/istanbul-lib-coverage@npm:2.0.1" - checksum: 10c0/2486204ab68c869928d96abd9ba4913050977b3fb26da1113156a7e6d2f203fec53d51ed9b9db3199a5eb75e69049ab600da953efd55fd9329e2c1482abb493e - languageName: node - linkType: hard - -"@types/istanbul-lib-report@npm:*": - version: 3.0.0 - resolution: "@types/istanbul-lib-report@npm:3.0.0" - dependencies: - "@types/istanbul-lib-coverage": "npm:*" - checksum: 10c0/7ced458631276a28082ee40645224c3cdd8b861961039ff811d841069171c987ec7e50bc221845ec0d04df0022b2f457a21fb2f816dab2fbe64d59377b32031f - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.0": - version: 3.0.1 - resolution: "@types/istanbul-reports@npm:3.0.1" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10c0/e147f0db9346a0cae9a359220bc76f7c78509fb6979a2597feb24d64b6e8328d2d26f9d152abbd59c6bca721e4ea2530af20116d01df50815efafd1e151fd777 - languageName: node - linkType: hard - "@types/jasmine@npm:3.10.18": version: 3.10.18 resolution: "@types/jasmine@npm:3.10.18" @@ -2016,7 +1908,7 @@ __metadata: languageName: node linkType: hard -"@types/normalize-package-data@npm:^2.4.0, @types/normalize-package-data@npm:^2.4.1": +"@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" checksum: 10c0/c90b163741f27a1a4c3b1869d7d5c272adbd355eb50d5f060f9ce122ce4342cf35f5b0005f55ef780596cacfeb69b7eee54cd3c2e02d37f75e664945b6e75fc6 @@ -2097,20 +1989,6 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.0": - version: 2.0.1 - resolution: "@types/stack-utils@npm:2.0.1" - checksum: 10c0/3327ee919a840ffe907bbd5c1d07dfd79137dd9732d2d466cf717ceec5bb21f66296173c53bb56cff95fae4185b9cd6770df3e9745fe4ba528bbc4975f54d13f - languageName: node - linkType: hard - -"@types/triple-beam@npm:^1.3.2": - version: 1.3.2 - resolution: "@types/triple-beam@npm:1.3.2" - checksum: 10c0/2e936cff7cde9df7da854a54a5f63e0a434b2ae1d6c1eb6de5f7a0b1107b023b3c272abecbba28614a54b8831226b29e37a49e3e34a7490a6a24d770a5b44eb9 - languageName: node - linkType: hard - "@types/which@npm:^2.0.1": version: 2.0.2 resolution: "@types/which@npm:2.0.2" @@ -2127,22 +2005,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs-parser@npm:*": - version: 15.0.0 - resolution: "@types/yargs-parser@npm:15.0.0" - checksum: 10c0/0dee6418ca20edd16686198485442780a2004aa53767fbf70f5b66a568a3c5e5f2fcdedcf5e0505c5065a2ab4dcf3353180a2db0ddc82470d0871f225e8da792 - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.8": - version: 17.0.24 - resolution: "@types/yargs@npm:17.0.24" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/fbebf57e1d04199e5e7eb0c67a402566fa27177ee21140664e63da826408793d203d262b48f8f41d4a7665126393d2e952a463e960e761226def247d9bbcdbd0 - languageName: node - linkType: hard - "@types/yauzl@npm:^2.9.1": version: 2.9.1 resolution: "@types/yauzl@npm:2.9.1" @@ -2152,29 +2014,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.16.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.16.0" - "@typescript-eslint/type-utils": "npm:8.16.0" - "@typescript-eslint/utils": "npm:8.16.0" - "@typescript-eslint/visitor-keys": "npm:8.16.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^1.3.0" - peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/b03612b726ee5aff631cd50e05ceeb06a522e64465e4efdc134e3a27a09406b959ef7a05ec4acef1956b3674dc4fedb6d3a62ce69382f9e30c227bd4093003e5 - languageName: node - linkType: hard - "@typescript-eslint/eslint-plugin@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/eslint-plugin@npm:8.21.0" @@ -2196,24 +2035,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/parser@npm:8.16.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:8.16.0" - "@typescript-eslint/types": "npm:8.16.0" - "@typescript-eslint/typescript-estree": "npm:8.16.0" - "@typescript-eslint/visitor-keys": "npm:8.16.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/e49c6640a7a863a16baecfbc5b99392a4731e9c7e9c9aaae4efbc354e305485fe0f39a28bf0acfae85bc01ce37fe0cc140fd315fdaca8b18f9b5e0addff8ceae - languageName: node - linkType: hard - "@typescript-eslint/parser@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/parser@npm:8.21.0" @@ -2230,16 +2051,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/scope-manager@npm:8.16.0" - dependencies: - "@typescript-eslint/types": "npm:8.16.0" - "@typescript-eslint/visitor-keys": "npm:8.16.0" - checksum: 10c0/23b7c738b83f381c6419a36e6ca951944187e3e00abb8e012bce8041880410fe498303e28bdeb0e619023a69b14cf32a5ec1f9427c5382807788cd8e52a46a6e - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/scope-manager@npm:8.21.0" @@ -2250,23 +2061,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/type-utils@npm:8.16.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.16.0" - "@typescript-eslint/utils": "npm:8.16.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^1.3.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/24c0e815c8bdf99bf488c7528bd6a7c790e8b3b674cb7fb075663afc2ee26b48e6f4cf7c0d14bb21e2376ca62bd8525cbcb5688f36135b00b62b1d353d7235b9 - languageName: node - linkType: hard - "@typescript-eslint/type-utils@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/type-utils@npm:8.21.0" @@ -2282,13 +2076,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/types@npm:8.16.0" - checksum: 10c0/141e257ab4060a9c0e2e14334ca14ab6be713659bfa38acd13be70a699fb5f36932a2584376b063063ab3d723b24bc703dbfb1ce57d61d7cfd7ec5bd8a975129 - languageName: node - linkType: hard - "@typescript-eslint/types@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/types@npm:8.21.0" @@ -2296,25 +2083,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.16.0" - dependencies: - "@typescript-eslint/types": "npm:8.16.0" - "@typescript-eslint/visitor-keys": "npm:8.16.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^1.3.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/f28fea5af4798a718b6735d1758b791a331af17386b83cb2856d89934a5d1693f7cb805e73c3b33f29140884ac8ead9931b1d7c3de10176fa18ca7a346fe10d0 - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/typescript-estree@npm:8.21.0" @@ -2333,23 +2101,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/utils@npm:8.16.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.16.0" - "@typescript-eslint/types": "npm:8.16.0" - "@typescript-eslint/typescript-estree": "npm:8.16.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/1e61187eef3da1ab1486d2a977d8f3b1cb8ef7fa26338500a17eb875ca42a8942ef3f2241f509eef74cf7b5620c109483afc7d83d5b0ab79b1e15920f5a49818 - languageName: node - linkType: hard - "@typescript-eslint/utils@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/utils@npm:8.21.0" @@ -2365,16 +2116,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.16.0": - version: 8.16.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.16.0" - dependencies: - "@typescript-eslint/types": "npm:8.16.0" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/537df37801831aa8d91082b2adbffafd40305ed4518f0e7d3cbb17cc466d8b9ac95ac91fa232e7fe585d7c522d1564489ec80052ebb2a6ab9bbf89ef9dd9b7bc - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:8.21.0": version: 8.21.0 resolution: "@typescript-eslint/visitor-keys@npm:8.21.0" @@ -2385,99 +2126,6 @@ __metadata: languageName: node linkType: hard -"@vitest/pretty-format@npm:2.0.5": - version: 2.0.5 - resolution: "@vitest/pretty-format@npm:2.0.5" - dependencies: - tinyrainbow: "npm:^1.2.0" - checksum: 10c0/236c0798c5170a0b5ad5d4bd06118533738e820b4dd30079d8fbcb15baee949d41c60f42a9f769906c4a5ce366d7ef11279546070646c0efc03128c220c31f37 - languageName: node - linkType: hard - -"@vitest/snapshot@npm:^1.2.2": - version: 1.2.2 - resolution: "@vitest/snapshot@npm:1.2.2" - dependencies: - magic-string: "npm:^0.30.5" - pathe: "npm:^1.1.1" - pretty-format: "npm:^29.7.0" - checksum: 10c0/0f8a69a289aa6466c7dd56f8327190d56a0bc7ad10412127de001c94784f6dba5e5bccb757def21f565f4efa3e00c307b92e8b6c302f11fc57889b743ba18a95 - languageName: node - linkType: hard - -"@vitest/snapshot@npm:^2.0.4": - version: 2.0.5 - resolution: "@vitest/snapshot@npm:2.0.5" - dependencies: - "@vitest/pretty-format": "npm:2.0.5" - magic-string: "npm:^0.30.10" - pathe: "npm:^1.1.2" - checksum: 10c0/7bf38474248f5ae0aac6afad511785d2b7a023ac5158803c2868fd172b5b9c1a569fb1dd64a09a49e43fd342cab71ea485ada89b7f08d37b1622a5a0ac00271d - languageName: node - linkType: hard - -"@wdio/browserstack-service@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/browserstack-service@npm:8.41.0" - dependencies: - "@browserstack/ai-sdk-node": "npm:1.5.9" - "@percy/appium-app": "npm:^2.0.1" - "@percy/selenium-webdriver": "npm:^2.0.3" - "@types/gitconfiglocal": "npm:^2.0.1" - "@wdio/logger": "npm:8.38.0" - "@wdio/reporter": "npm:8.41.0" - "@wdio/types": "npm:8.41.0" - browserstack-local: "npm:^1.5.1" - chalk: "npm:^5.3.0" - csv-writer: "npm:^1.6.0" - formdata-node: "npm:5.0.1" - git-repo-info: "npm:^2.1.1" - gitconfiglocal: "npm:^2.1.0" - got: "npm:^12.6.1" - uuid: "npm:^10.0.0" - webdriverio: "npm:8.41.0" - winston-transport: "npm:^4.5.0" - yauzl: "npm:^3.0.0" - peerDependencies: - "@wdio/cli": ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/d495f34c0763f391efdcf070fec9714a7ada84f986cb2be8d7b732c5c90463e2b55e7b0f648e0eebc10d802a011ba29bb4bfe0befb6a19c9fcf34d5eefb44847 - languageName: node - linkType: hard - -"@wdio/cli@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/cli@npm:8.41.0" - dependencies: - "@types/node": "npm:^22.2.0" - "@vitest/snapshot": "npm:^2.0.4" - "@wdio/config": "npm:8.41.0" - "@wdio/globals": "npm:8.41.0" - "@wdio/logger": "npm:8.38.0" - "@wdio/protocols": "npm:8.40.3" - "@wdio/types": "npm:8.41.0" - "@wdio/utils": "npm:8.41.0" - async-exit-hook: "npm:^2.0.1" - chalk: "npm:^5.2.0" - chokidar: "npm:^4.0.0" - cli-spinners: "npm:^2.9.0" - dotenv: "npm:^16.3.1" - ejs: "npm:^3.1.9" - execa: "npm:^8.0.1" - import-meta-resolve: "npm:^4.0.0" - inquirer: "npm:9.2.12" - lodash.flattendeep: "npm:^4.4.0" - lodash.pickby: "npm:^4.6.0" - lodash.union: "npm:^4.6.0" - read-pkg-up: "npm:10.0.0" - recursive-readdir: "npm:^2.2.3" - webdriverio: "npm:8.41.0" - yargs: "npm:^17.7.2" - bin: - wdio: bin/wdio.js - checksum: 10c0/9394eace29b54488485ca7249ae30f274e7b6a37329c2bea24c97fbf80a223e8764b41d22f3a8571954e2d46b3337b4bac55756f0d29de419e4c4a00102c8285 - languageName: node - linkType: hard - "@wdio/config@npm:8.41.0": version: 8.41.0 resolution: "@wdio/config@npm:8.41.0" @@ -2493,64 +2141,6 @@ __metadata: languageName: node linkType: hard -"@wdio/globals@npm:8.41.0, @wdio/globals@npm:^8.29.3": - version: 8.41.0 - resolution: "@wdio/globals@npm:8.41.0" - dependencies: - expect-webdriverio: "npm:^4.11.2" - webdriverio: "npm:8.41.0" - dependenciesMeta: - expect-webdriverio: - optional: true - webdriverio: - optional: true - checksum: 10c0/bd7688bcb7b4e16afac528c8577458585b4dccb7515dce4d2726e9a8030666737ea1fda81bcf3cbfa33a0530d0d4630f804a57ba01d528160cba17b217f465c7 - languageName: node - linkType: hard - -"@wdio/jasmine-framework@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/jasmine-framework@npm:8.41.0" - dependencies: - "@types/node": "npm:^22.2.0" - "@wdio/globals": "npm:8.41.0" - "@wdio/logger": "npm:8.38.0" - "@wdio/types": "npm:8.41.0" - "@wdio/utils": "npm:8.41.0" - expect-webdriverio: "npm:^4.11.2" - jasmine: "npm:^5.0.0" - checksum: 10c0/ef9c869f6852013e5ffee99278a4da1678d509963329289f50c0aaed4c31079b78d950afe1efe2ac46d2ac6045d0da339195def9f8a3ecaba519086759293002 - languageName: node - linkType: hard - -"@wdio/junit-reporter@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/junit-reporter@npm:8.41.0" - dependencies: - "@wdio/reporter": "npm:8.41.0" - "@wdio/types": "npm:8.41.0" - json-stringify-safe: "npm:^5.0.1" - junit-report-builder: "npm:^5.0.0" - checksum: 10c0/2c00b1e10b55aadf6f185b0d89ea5227ec21f4e00ad58f94e9bacc27425e1bfec3ca9dae5d8540a1df984f9f71488223477faa230294026db276f0b0607ed33f - languageName: node - linkType: hard - -"@wdio/local-runner@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/local-runner@npm:8.41.0" - dependencies: - "@types/node": "npm:^22.2.0" - "@wdio/logger": "npm:8.38.0" - "@wdio/repl": "npm:8.40.3" - "@wdio/runner": "npm:8.41.0" - "@wdio/types": "npm:8.41.0" - async-exit-hook: "npm:^2.0.1" - split2: "npm:^4.1.0" - stream-buffers: "npm:^3.0.2" - checksum: 10c0/c86f546449a8618e1bce223d0abf26360bd05867ae6c217bc90205eb2c4d3311f6c188e46c49e178bd3a687f9d44eaf7e45cca7e4764c023915e534a9670965a - languageName: node - linkType: hard - "@wdio/logger@npm:8.38.0, @wdio/logger@npm:^8.11.0, @wdio/logger@npm:^8.28.0": version: 8.38.0 resolution: "@wdio/logger@npm:8.38.0" @@ -2579,51 +2169,6 @@ __metadata: languageName: node linkType: hard -"@wdio/reporter@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/reporter@npm:8.41.0" - dependencies: - "@types/node": "npm:^22.2.0" - "@wdio/logger": "npm:8.38.0" - "@wdio/types": "npm:8.41.0" - diff: "npm:^7.0.0" - object-inspect: "npm:^1.12.0" - checksum: 10c0/a8158fd0508b8018d35958825193c8e4b835b8c179a9bb20dc7869346bc6cdd50804917efef8632e0563fc6dca37a2f1915a86baa93b7f1cb5ee4efd0ad193b4 - languageName: node - linkType: hard - -"@wdio/runner@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/runner@npm:8.41.0" - dependencies: - "@types/node": "npm:^22.2.0" - "@wdio/config": "npm:8.41.0" - "@wdio/globals": "npm:8.41.0" - "@wdio/logger": "npm:8.38.0" - "@wdio/types": "npm:8.41.0" - "@wdio/utils": "npm:8.41.0" - deepmerge-ts: "npm:^5.1.0" - expect-webdriverio: "npm:^4.12.0" - gaze: "npm:^1.1.3" - webdriver: "npm:8.41.0" - webdriverio: "npm:8.41.0" - checksum: 10c0/97f22ad0cc48bf6b66d95345d4000b1cf484a39606766db7d15708324d62913369acf043db8f81e3a56cedba3188e4137551df1f7bc409cf399d9d274da483ed - languageName: node - linkType: hard - -"@wdio/spec-reporter@npm:8.41.0": - version: 8.41.0 - resolution: "@wdio/spec-reporter@npm:8.41.0" - dependencies: - "@wdio/reporter": "npm:8.41.0" - "@wdio/types": "npm:8.41.0" - chalk: "npm:^5.1.2" - easy-table: "npm:^1.2.0" - pretty-ms: "npm:^7.0.0" - checksum: 10c0/ca472c2066b14d341df3960a3b4bb64be1c9170023a1839267c9f84bd1018e2bd7ec46fe65d1e88149dbfe42ef1e1018525558fc9df28a1b4ecb1ba7779e1cfd - languageName: node - linkType: hard - "@wdio/types@npm:8.41.0": version: 8.41.0 resolution: "@wdio/types@npm:8.41.0" @@ -3082,7 +2627,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.2.1": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -3374,13 +2919,6 @@ __metadata: languageName: node linkType: hard -"async-exit-hook@npm:^2.0.1": - version: 2.0.1 - resolution: "async-exit-hook@npm:2.0.1" - checksum: 10c0/81407a440ef0aab328df2369f1a9d957ee53e9a5a43e3b3dcb2be05151a68de0e4ff5e927f4718c88abf85800731f5b3f69a47a6642ce135f5e7d43ca0fce41d - languageName: node - linkType: hard - "async@npm:^3.2.3, async@npm:^3.2.4": version: 3.2.4 resolution: "async@npm:3.2.4" @@ -3621,14 +3159,6 @@ __metadata: "@types/express": "npm:4.17.21" "@types/jasmine": "npm:3.10.18" "@types/node": "npm:22.10.2" - "@typescript-eslint/eslint-plugin": "npm:8.16.0" - "@typescript-eslint/parser": "npm:8.16.0" - "@wdio/browserstack-service": "npm:8.41.0" - "@wdio/cli": "npm:8.41.0" - "@wdio/jasmine-framework": "npm:8.41.0" - "@wdio/junit-reporter": "npm:8.41.0" - "@wdio/local-runner": "npm:8.41.0" - "@wdio/spec-reporter": "npm:8.41.0" ajv: "npm:6.12.6" browserstack-local: "npm:1.5.6" chrome-webstore-upload: "npm:3.1.4" @@ -3688,7 +3218,7 @@ __metadata: languageName: node linkType: hard -"browserstack-local@npm:1.5.6, browserstack-local@npm:^1.3.7, browserstack-local@npm:^1.5.1": +"browserstack-local@npm:1.5.6, browserstack-local@npm:^1.3.7": version: 1.5.6 resolution: "browserstack-local@npm:1.5.6" dependencies: @@ -3965,7 +3495,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.1.2, chalk@npm:^5.2.0, chalk@npm:^5.3.0": +"chalk@npm:^5.1.2": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 @@ -3998,15 +3528,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^4.0.0": - version: 4.0.1 - resolution: "chokidar@npm:4.0.1" - dependencies: - readdirp: "npm:^4.0.1" - checksum: 10c0/4bb7a3adc304059810bb6c420c43261a15bb44f610d77c35547addc84faa0374265c3adc67f25d06f363d9a4571962b02679268c40de07676d260de1986efea9 - languageName: node - linkType: hard - "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -4134,7 +3655,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.0": +"cli-spinners@npm:^2.5.0": version: 2.9.0 resolution: "cli-spinners@npm:2.9.0" checksum: 10c0/c0d5437acc1ace7361b1c58a4fda3c92c2d8691ff3169ac658ce30faee71280b7aa706c072bcb6d0e380c232f3495f7d5ad4668c1391fe02c4d3a39d37798f44 @@ -4148,13 +3669,6 @@ __metadata: languageName: node linkType: hard -"cli-width@npm:^4.1.0": - version: 4.1.0 - resolution: "cli-width@npm:4.1.0" - checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f - languageName: node - linkType: hard - "cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -4729,13 +4243,6 @@ __metadata: languageName: node linkType: hard -"csv-writer@npm:^1.6.0": - version: 1.6.0 - resolution: "csv-writer@npm:1.6.0" - checksum: 10c0/9d30518c6cc7489e0cd8a3a83d6b014c27ed537d6e4a866f901065ddf49e06e4815a4204ce02c3c0024fd8e648e4a2efbdb318a1c63ddb3026282682d3a665e3 - languageName: node - linkType: hard - "custom-event@npm:~1.0.0": version: 1.0.1 resolution: "custom-event@npm:1.0.1" @@ -4830,7 +4337,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -5088,13 +4595,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^7.0.0": - version: 7.0.0 - resolution: "diff@npm:7.0.0" - checksum: 10c0/251fd15f85ffdf814cfc35a728d526b8d2ad3de338dcbd011ac6e57c461417090766b28995f8ff733135b5fbc3699c392db1d5e27711ac4e00244768cd1d577b - languageName: node - linkType: hard - "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -5200,7 +4700,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.3.1, dotenv@npm:^16.4.4, dotenv@npm:~16.4.5": +"dotenv@npm:^16.4.4, dotenv@npm:~16.4.5": version: 16.4.5 resolution: "dotenv@npm:16.4.5" checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f @@ -5230,19 +4730,6 @@ __metadata: languageName: node linkType: hard -"easy-table@npm:^1.2.0": - version: 1.2.0 - resolution: "easy-table@npm:1.2.0" - dependencies: - ansi-regex: "npm:^5.0.1" - wcwidth: "npm:^1.0.1" - dependenciesMeta: - wcwidth: - optional: true - checksum: 10c0/2d37937cd608586ba02e1ec479f90ccec581d366b3b0d1bb26b99ee6005f8d724e32a07a873759893461ca45b99e2d08c30326529d967ce9eedc1e9b68d4aa63 - languageName: node - linkType: hard - "edge-paths@npm:^3.0.5": version: 3.0.5 resolution: "edge-paths@npm:3.0.5" @@ -5276,7 +4763,7 @@ __metadata: languageName: node linkType: hard -"ejs@npm:^3.1.7, ejs@npm:^3.1.9": +"ejs@npm:^3.1.7": version: 3.1.10 resolution: "ejs@npm:3.1.10" dependencies: @@ -5455,7 +4942,7 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.3.1, error-ex@npm:^1.3.2": +"error-ex@npm:^1.3.1": version: 1.3.2 resolution: "error-ex@npm:1.3.2" dependencies: @@ -5663,13 +5150,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -5677,13 +5157,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^5.0.0": - version: 5.0.0 - resolution: "escape-string-regexp@npm:5.0.0" - checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 - languageName: node - linkType: hard - "escodegen@npm:^2.1.0": version: 2.1.0 resolution: "escodegen@npm:2.1.0" @@ -6033,23 +5506,6 @@ __metadata: languageName: node linkType: hard -"execa@npm:^8.0.1": - version: 8.0.1 - resolution: "execa@npm:8.0.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^8.0.1" - human-signals: "npm:^5.0.0" - is-stream: "npm:^3.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^5.1.0" - onetime: "npm:^6.0.0" - signal-exit: "npm:^4.1.0" - strip-final-newline: "npm:^3.0.0" - checksum: 10c0/2c52d8775f5bf103ce8eec9c7ab3059909ba350a5164744e9947ed14a53f51687c040a250bda833f906d1283aa8803975b84e6c8f7a7c42f99dc8ef80250d1af - languageName: node - linkType: hard - "exit-on-epipe@npm:~1.0.1": version: 1.0.1 resolution: "exit-on-epipe@npm:1.0.1" @@ -6057,41 +5513,6 @@ __metadata: languageName: node linkType: hard -"expect-webdriverio@npm:^4.11.2, expect-webdriverio@npm:^4.12.0": - version: 4.12.2 - resolution: "expect-webdriverio@npm:4.12.2" - dependencies: - "@vitest/snapshot": "npm:^1.2.2" - "@wdio/globals": "npm:^8.29.3" - "@wdio/logger": "npm:^8.28.0" - expect: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - lodash.isequal: "npm:^4.5.0" - webdriverio: "npm:^8.29.3" - dependenciesMeta: - "@wdio/globals": - optional: true - "@wdio/logger": - optional: true - webdriverio: - optional: true - checksum: 10c0/bc5d124893bd7f1ee310eed4f91a7b2eb45b15995f021aea5a037f5ccfe63211fef0c975aca79a868c1c9345ff2911bd46dd81763cc190666bc4e53549c99df5 - languageName: node - linkType: hard - -"expect@npm:^29.7.0": - version: 29.7.0 - resolution: "expect@npm:29.7.0" - dependencies: - "@jest/expect-utils": "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 - languageName: node - linkType: hard - "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -6154,7 +5575,7 @@ __metadata: languageName: node linkType: hard -"external-editor@npm:^3.0.3, external-editor@npm:^3.1.0": +"external-editor@npm:^3.0.3": version: 3.1.0 resolution: "external-editor@npm:3.1.0" dependencies: @@ -6255,13 +5676,6 @@ __metadata: languageName: node linkType: hard -"fecha@npm:^4.2.0": - version: 4.2.3 - resolution: "fecha@npm:4.2.3" - checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf - languageName: node - linkType: hard - "fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": version: 3.1.5 resolution: "fetch-blob@npm:3.1.5" @@ -6281,16 +5695,6 @@ __metadata: languageName: node linkType: hard -"figures@npm:^5.0.0": - version: 5.0.0 - resolution: "figures@npm:5.0.0" - dependencies: - escape-string-regexp: "npm:^5.0.0" - is-unicode-supported: "npm:^1.2.0" - checksum: 10c0/ce0f17d4ea8b0fc429c5207c343534a2f5284ecfb22aa08607da7dc84ed9e1cf754f5b97760e8dcb98d3c9d1a1e4d3d578fe3b5b99c426f05d0f06c7ba618e16 - languageName: node - linkType: hard - "file-entry-cache@npm:^8.0.0": version: 8.0.0 resolution: "file-entry-cache@npm:8.0.0" @@ -6377,16 +5781,6 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^6.3.0": - version: 6.3.0 - resolution: "find-up@npm:6.3.0" - dependencies: - locate-path: "npm:^7.1.0" - path-exists: "npm:^5.0.0" - checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 - languageName: node - linkType: hard - "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -6460,16 +5854,6 @@ __metadata: languageName: node linkType: hard -"formdata-node@npm:5.0.1": - version: 5.0.1 - resolution: "formdata-node@npm:5.0.1" - dependencies: - node-domexception: "npm:1.0.0" - web-streams-polyfill: "npm:4.0.0-beta.3" - checksum: 10c0/5254255d85f82308020d389818e38fceb93005a523744e0da9b7e1739910a05d3c6390887d6c4075385234cd7f0b79a1032a49ac905c1bfe2f4d043283263dfc - languageName: node - linkType: hard - "formdata-polyfill@npm:^4.0.10": version: 4.0.10 resolution: "formdata-polyfill@npm:4.0.10" @@ -6647,15 +6031,6 @@ __metadata: languageName: node linkType: hard -"gaze@npm:^1.1.3": - version: 1.1.3 - resolution: "gaze@npm:1.1.3" - dependencies: - globule: "npm:^1.0.0" - checksum: 10c0/5369619e23f6585e3a5efc4b8fad3b9f129fb4a88685bf0d6a98ca5ea0adb3868ede3d05643101deb03c42e15a0d36182d37f0122945935d05eddc82f4d79bfe - languageName: node - linkType: hard - "geckodriver@npm:~4.2.0": version: 4.2.1 resolution: "geckodriver@npm:4.2.1" @@ -6766,13 +6141,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^8.0.1": - version: 8.0.1 - resolution: "get-stream@npm:8.0.1" - checksum: 10c0/5c2181e98202b9dae0bb4a849979291043e5892eb40312b47f0c22b9414fc9b28a3b6063d2375705eb24abc41ecf97894d9a51f64ff021511b504477b27b4290 - languageName: node - linkType: hard - "get-symbol-description@npm:^1.0.2": version: 1.0.2 resolution: "get-symbol-description@npm:1.0.2" @@ -6819,13 +6187,6 @@ __metadata: languageName: node linkType: hard -"git-repo-info@npm:^2.1.1": - version: 2.1.1 - resolution: "git-repo-info@npm:2.1.1" - checksum: 10c0/894d03a1c8338ccddf7de3cd9ddbae5c16371164b84649ed5dfec9a1c7efbc761885f0f541a21bd033c07b91203ab315a63c77f36b142f18ad0f17a9699eb028 - languageName: node - linkType: hard - "git-semver-tags@npm:^5.0.0": version: 5.0.0 resolution: "git-semver-tags@npm:5.0.0" @@ -6866,15 +6227,6 @@ __metadata: languageName: node linkType: hard -"gitconfiglocal@npm:^2.1.0": - version: 2.1.0 - resolution: "gitconfiglocal@npm:2.1.0" - dependencies: - ini: "npm:^1.3.2" - checksum: 10c0/0882267ff1f7d13c2ab42f55bf1e329505054811862f1ae36b650b91f1fe4ea2fb85ef2bb9695b81454330fa30b8bbc179c69886d2e88e5ab2cc998eee3b02af - languageName: node - linkType: hard - "glob-parent@npm:6.0.2, glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2": version: 6.0.2 resolution: "glob-parent@npm:6.0.2" @@ -6982,20 +6334,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:~7.1.1": - version: 7.1.6 - resolution: "glob@npm:7.1.6" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.0.4" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/2575cce9306ac534388db751f0aa3e78afedb6af8f3b529ac6b2354f66765545145dba8530abf7bff49fb399a047d3f9b6901c38ee4c9503f592960d9af67763 - languageName: node - linkType: hard - "globals@npm:15.14.0, globals@npm:^15.9.0": version: 15.14.0 resolution: "globals@npm:15.14.0" @@ -7054,17 +6392,6 @@ __metadata: languageName: node linkType: hard -"globule@npm:^1.0.0": - version: 1.2.1 - resolution: "globule@npm:1.2.1" - dependencies: - glob: "npm:~7.1.1" - lodash: "npm:~4.17.10" - minimatch: "npm:~3.0.2" - checksum: 10c0/a0cfd9fb6511a1fd0729fd4a4240fb7283afb19603f5e9e00597d9806deaaaaf048091c43238fc2e3fec9fd0328355f0da00c3ab6c1af9850e394ef34e5845e9 - languageName: node - linkType: hard - "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -7093,7 +6420,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:4.2.11, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:4.2.11, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.10, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -7217,13 +6544,6 @@ __metadata: languageName: node linkType: hard -"headers-utils@npm:^1.2.0": - version: 1.2.5 - resolution: "headers-utils@npm:1.2.5" - checksum: 10c0/b0777bf1b5da6d60ce3da399efec37c40e4b32e2eed832e95f9c3b491ef28bfaf1c631e9825d910062a42a850a2dea7aab4d345afbc4ffc3d5e05b02c6e6ea8a - languageName: node - linkType: hard - "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -7240,15 +6560,6 @@ __metadata: languageName: node linkType: hard -"hosted-git-info@npm:^6.0.0": - version: 6.1.1 - resolution: "hosted-git-info@npm:6.1.1" - dependencies: - lru-cache: "npm:^7.5.1" - checksum: 10c0/ba7158f81ae29c1b5a1e452fa517082f928051da8797a00788a84ff82b434996d34f78a875bbb688aec162bda1d4cf71d2312f44da3c896058803f5efa6ce77f - languageName: node - linkType: hard - "hosted-git-info@npm:^7.0.0, hosted-git-info@npm:^7.0.2": version: 7.0.2 resolution: "hosted-git-info@npm:7.0.2" @@ -7410,14 +6721,7 @@ __metadata: "human-signals@npm:^2.1.0": version: 2.1.0 resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - -"human-signals@npm:^5.0.0": - version: 5.0.0 - resolution: "human-signals@npm:5.0.0" - checksum: 10c0/5a9359073fe17a8b58e5a085e9a39a950366d9f00217c4ff5878bd312e09d80f460536ea6a3f260b5943a01fe55c158d1cea3fc7bee3d0520aeef04f6d915c82 + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a languageName: node linkType: hard @@ -7583,29 +6887,6 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:9.2.12": - version: 9.2.12 - resolution: "inquirer@npm:9.2.12" - dependencies: - "@ljharb/through": "npm:^2.3.11" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^5.3.0" - cli-cursor: "npm:^3.1.0" - cli-width: "npm:^4.1.0" - external-editor: "npm:^3.1.0" - figures: "npm:^5.0.0" - lodash: "npm:^4.17.21" - mute-stream: "npm:1.0.0" - ora: "npm:^5.4.1" - run-async: "npm:^3.0.0" - rxjs: "npm:^7.8.1" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^6.2.0" - checksum: 10c0/efc19864bea5f4b22a47e686aa88684ee42352db4e96dd6307da7140496c16e5ef0e74be664fba490b068714dc24d72f66dc1907a1ccbaf9d58d6156cbdc5908 - languageName: node - linkType: hard - "inquirer@npm:^8.2.4": version: 8.2.4 resolution: "inquirer@npm:8.2.4" @@ -7736,7 +7017,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.5.0, is-core-module@npm:^2.8.1": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.5.0": version: 2.15.1 resolution: "is-core-module@npm:2.15.1" dependencies: @@ -7923,13 +7204,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "is-stream@npm:3.0.0" - checksum: 10c0/eb2f7127af02ee9aa2a0237b730e47ac2de0d4e76a4a905a50a11557f2339df5765eaea4ceb8029f1efa978586abe776908720bfcb1900c20c6ec5145f6f29d8 - languageName: node - linkType: hard - "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -7973,13 +7247,6 @@ __metadata: languageName: node linkType: hard -"is-unicode-supported@npm:^1.2.0": - version: 1.3.0 - resolution: "is-unicode-supported@npm:1.3.0" - checksum: 10c0/b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a - languageName: node - linkType: hard - "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -8154,26 +7421,7 @@ __metadata: languageName: node linkType: hard -"jasmine-core@npm:~5.0.0": - version: 5.0.0 - resolution: "jasmine-core@npm:5.0.0" - checksum: 10c0/b4c67d2a60bbf56e28b58098ceec1addd4ff6079e491d8017765615bb5ab4af2c38078a08a7a88fa11dd09b21c8a2d38e59b726f06bb0ff219c739dd3555cc03 - languageName: node - linkType: hard - -"jasmine@npm:^5.0.0": - version: 5.0.0 - resolution: "jasmine@npm:5.0.0" - dependencies: - glob: "npm:^10.2.2" - jasmine-core: "npm:~5.0.0" - bin: - jasmine: bin/jasmine.js - checksum: 10c0/467193d5dac69330a51afc0013ee04abca071ec8f6900b5d5af089043eb74d9e4aec336f774c26ef1adc8c701d15be95273e9fc57d82a94936cf5b4a66a8c2c9 - languageName: node - linkType: hard - -"jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": +"jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -8192,49 +7440,6 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" - dependencies: - chalk: "npm:^4.0.0" - jest-diff: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - pretty-format: "npm:^29.7.0" - checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e - languageName: node - linkType: hard - -"jest-message-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-message-util@npm:29.7.0" - dependencies: - "@babel/code-frame": "npm:^7.12.13" - "@jest/types": "npm:^29.6.3" - "@types/stack-utils": "npm:^2.0.0" - chalk: "npm:^4.0.0" - graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.4" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 - languageName: node - linkType: hard - -"jest-util@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-util@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - "@types/node": "npm:*" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - graceful-fs: "npm:^4.2.9" - picomatch: "npm:^2.2.3" - checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 - languageName: node - linkType: hard - "jest-worker@npm:^27.4.5": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -8498,17 +7703,6 @@ __metadata: languageName: node linkType: hard -"junit-report-builder@npm:^5.0.0": - version: 5.0.0 - resolution: "junit-report-builder@npm:5.0.0" - dependencies: - lodash: "npm:^4.17.21" - make-dir: "npm:^3.1.0" - xmlbuilder: "npm:^15.1.1" - checksum: 10c0/e056b900901585095b2c072e5c10c5e93ad76e9ebdbf6e13203f950516164f23a485590648854fece71140dcaa0294533d20a639ff9ad463d8cd1b20518e1131 - languageName: node - linkType: hard - "just-diff-apply@npm:^5.2.0": version: 5.5.0 resolution: "just-diff-apply@npm:5.5.0" @@ -8816,7 +8010,7 @@ __metadata: languageName: node linkType: hard -"lines-and-columns@npm:2.0.3, lines-and-columns@npm:^2.0.3": +"lines-and-columns@npm:2.0.3": version: 2.0.3 resolution: "lines-and-columns@npm:2.0.3" checksum: 10c0/09525c10010a925b7efe858f1dd3184eeac34f0a9bc34993075ec490efad71e948147746b18e9540279cc87cd44085b038f986903db3de65ffe96d38a7b91c4c @@ -8918,15 +8112,6 @@ __metadata: languageName: node linkType: hard -"locate-path@npm:^7.1.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 - languageName: node - linkType: hard - "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0" @@ -8934,20 +8119,6 @@ __metadata: languageName: node linkType: hard -"lodash.flattendeep@npm:^4.4.0": - version: 4.4.0 - resolution: "lodash.flattendeep@npm:4.4.0" - checksum: 10c0/83cb80754b921fb4ed2c222b91a82b2524f3bdc60c3ae91e00688bd4bf1bcc28b8a2cc250e11fdc1b6da3a2de09e57008e13f15a209cafdd4f9163d047f97544 - languageName: node - linkType: hard - -"lodash.isequal@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - "lodash.ismatch@npm:^4.4.0": version: 4.4.0 resolution: "lodash.ismatch@npm:4.4.0" @@ -8962,20 +8133,6 @@ __metadata: languageName: node linkType: hard -"lodash.pickby@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.pickby@npm:4.6.0" - checksum: 10c0/46befadb64ab0f61159977174b291f87b005cec1c7bd73d1b6949ec4cdff483c1be0e34398df8955b76ce06a3e93a4a5c5a552a4299520390d6993c5420c7ab9 - languageName: node - linkType: hard - -"lodash.union@npm:^4.6.0": - version: 4.6.0 - resolution: "lodash.union@npm:4.6.0" - checksum: 10c0/6da7f72d1facd472f6090b49eefff984c9f9179e13172039c0debca6851d21d37d83c7ad5c43af23bd220f184cd80e6897e8e3206509fae491f9068b02ae6319 - languageName: node - linkType: hard - "lodash.zip@npm:^4.2.0": version: 4.2.0 resolution: "lodash.zip@npm:4.2.0" @@ -8983,7 +8140,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:~4.17.10": +"lodash@npm:^4.17.15, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c @@ -9013,20 +8170,6 @@ __metadata: languageName: node linkType: hard -"logform@npm:^2.3.2": - version: 2.5.1 - resolution: "logform@npm:2.5.1" - dependencies: - "@colors/colors": "npm:1.5.0" - "@types/triple-beam": "npm:^1.3.2" - fecha: "npm:^4.2.0" - ms: "npm:^2.1.1" - safe-stable-stringify: "npm:^2.3.1" - triple-beam: "npm:^1.3.0" - checksum: 10c0/d11c36b4c42063abc816fda2fd149cff9969a9943d42afd95ddd1426804980b4e92e24f2ea6a9916fd490224b1c97578734a37d3b40ce3a9418495ce52e8ef23 - languageName: node - linkType: hard - "loglevel-plugin-prefix@npm:^0.8.4": version: 0.8.4 resolution: "loglevel-plugin-prefix@npm:0.8.4" @@ -9091,7 +8234,7 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^7.14.1, lru-cache@npm:^7.5.1, lru-cache@npm:^7.7.1": +"lru-cache@npm:^7.14.1, lru-cache@npm:^7.7.1": version: 7.18.3 resolution: "lru-cache@npm:7.18.3" checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed @@ -9107,15 +8250,6 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.10, magic-string@npm:^0.30.5": - version: 0.30.11 - resolution: "magic-string@npm:0.30.11" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/b9eb370773d0bd90ca11a848753409d8e5309b1ad56d2a1aa49d6649da710a6d2fe7237ad1a643c5a5d3800de2b9946ed9690acdfc00e6cc1aeafff3ab1752c4 - languageName: node - linkType: hard - "make-dir@npm:4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -9135,7 +8269,7 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": +"make-dir@npm:^3.0.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -9374,13 +8508,6 @@ __metadata: languageName: node linkType: hard -"mimic-fn@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-fn@npm:4.0.0" - checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf - languageName: node - linkType: hard - "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -9429,7 +8556,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -9465,15 +8592,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:~3.0.2": - version: 3.0.8 - resolution: "minimatch@npm:3.0.8" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/72b226f452dcfb5075255f53534cb83fc25565b909e79b9be4fad463d735cb1084827f7013ff41d050e77ee6e474408c6073473edd2fb72c2fd630cfb0acc6ad - languageName: node - linkType: hard - "minimist-options@npm:4.1.0": version: 4.1.0 resolution: "minimist-options@npm:4.1.0" @@ -9698,7 +8816,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:1.0.0, mute-stream@npm:^1.0.0, mute-stream@npm:~1.0.0": +"mute-stream@npm:^1.0.0, mute-stream@npm:~1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" checksum: 10c0/dce2a9ccda171ec979a3b4f869a102b1343dee35e920146776780de182f16eae459644d187e38d59a3d37adf85685e1c17c38cf7bfda7e39a9880f7a1d10a74c @@ -9784,7 +8902,7 @@ __metadata: languageName: node linkType: hard -"node-domexception@npm:1.0.0, node-domexception@npm:^1.0.0": +"node-domexception@npm:^1.0.0": version: 1.0.0 resolution: "node-domexception@npm:1.0.0" checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b @@ -9891,18 +9009,6 @@ __metadata: languageName: node linkType: hard -"node-request-interceptor@npm:^0.6.3": - version: 0.6.3 - resolution: "node-request-interceptor@npm:0.6.3" - dependencies: - "@open-draft/until": "npm:^1.0.3" - debug: "npm:^4.3.0" - headers-utils: "npm:^1.2.0" - strict-event-emitter: "npm:^0.1.0" - checksum: 10c0/d210e40d16da68719d8e676957a05ae5c47357a0eead0e52df1977d1fc2a0fbaf8dac70792e5305961bd2126f71bb38c452ded44fcf2472b4d9ece8e437ca449 - languageName: node - linkType: hard - "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -9949,18 +9055,6 @@ __metadata: languageName: node linkType: hard -"normalize-package-data@npm:^5.0.0": - version: 5.0.0 - resolution: "normalize-package-data@npm:5.0.0" - dependencies: - hosted-git-info: "npm:^6.0.0" - is-core-module: "npm:^2.8.1" - semver: "npm:^7.3.5" - validate-npm-package-license: "npm:^3.0.4" - checksum: 10c0/705fe66279edad2f93f6e504d5dc37984e404361a3df921a76ab61447eb285132d20ff261cc0bee9566b8ce895d75fcfec913417170add267e2873429fe38392 - languageName: node - linkType: hard - "normalize-package-data@npm:^6.0.0, normalize-package-data@npm:^6.0.1": version: 6.0.2 resolution: "normalize-package-data@npm:6.0.2" @@ -10090,15 +9184,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^5.1.0": - version: 5.1.0 - resolution: "npm-run-path@npm:5.1.0" - dependencies: - path-key: "npm:^4.0.0" - checksum: 10c0/ff6d77514489f47fa1c3b1311d09cd4b6d09a874cc1866260f9dea12cbaabda0436ed7f8c2ee44d147bf99a3af29307c6f63b0f83d242b0b6b0ab25dff2629e3 - languageName: node - linkType: hard - "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -10209,7 +9294,7 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.12.0, object-inspect@npm:^1.13.1": +"object-inspect@npm:^1.13.1": version: 1.13.1 resolution: "object-inspect@npm:1.13.1" checksum: 10c0/fad603f408e345c82e946abdf4bfd774260a5ed3e5997a0b057c44153ac32c7271ff19e3a5ae39c858da683ba045ccac2f65245c12763ce4e8594f818f4a648d @@ -10305,15 +9390,6 @@ __metadata: languageName: node linkType: hard -"onetime@npm:^6.0.0": - version: 6.0.0 - resolution: "onetime@npm:6.0.0" - dependencies: - mimic-fn: "npm:^4.0.0" - checksum: 10c0/4eef7c6abfef697dd4479345a4100c382d73c149d2d56170a54a07418c50816937ad09500e1ed1e79d235989d073a9bade8557122aee24f0576ecde0f392bb6c - languageName: node - linkType: hard - "open@npm:^8.4.0": version: 8.4.0 resolution: "open@npm:8.4.0" @@ -10420,15 +9496,6 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad - languageName: node - linkType: hard - "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -10456,15 +9523,6 @@ __metadata: languageName: node linkType: hard -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 - languageName: node - linkType: hard - "p-map-series@npm:2.1.0": version: 2.1.0 resolution: "p-map-series@npm:2.1.0" @@ -10673,26 +9731,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^7.0.0": - version: 7.0.0 - resolution: "parse-json@npm:7.0.0" - dependencies: - "@babel/code-frame": "npm:^7.21.4" - error-ex: "npm:^1.3.2" - json-parse-even-better-errors: "npm:^3.0.0" - lines-and-columns: "npm:^2.0.3" - type-fest: "npm:^3.8.0" - checksum: 10c0/d7326dee546baab677208a3e829674bdbd383494b1103c7f6f60ed14d5b9f516a25243e3f5726dbba16e59e7089213b8c706bef4545a0108a0d282b2f94c8427 - languageName: node - linkType: hard - -"parse-ms@npm:^2.1.0": - version: 2.1.0 - resolution: "parse-ms@npm:2.1.0" - checksum: 10c0/9c5c0a95c6267c84085685556a6e102ee806c3147ec11cbb9b98e35998eb4a48a757bd6ea7bfd930062de65909a33d24985055b4394e70aa0b65ee40cef16911 - languageName: node - linkType: hard - "parse-path@npm:^7.0.0": version: 7.0.0 resolution: "parse-path@npm:7.0.0" @@ -10742,13 +9780,6 @@ __metadata: languageName: node linkType: hard -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a - languageName: node - linkType: hard - "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -10770,13 +9801,6 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^4.0.0": - version: 4.0.0 - resolution: "path-key@npm:4.0.0" - checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 - languageName: node - linkType: hard - "path-parse@npm:^1.0.7": version: 1.0.7 resolution: "path-parse@npm:1.0.7" @@ -10834,13 +9858,6 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^1.1.1, pathe@npm:^1.1.2": - version: 1.1.2 - resolution: "pathe@npm:1.1.2" - checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897 - languageName: node - linkType: hard - "pause-stream@npm:0.0.11": version: 0.0.11 resolution: "pause-stream@npm:0.0.11" @@ -10876,7 +9893,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be @@ -11085,15 +10102,6 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^7.0.0": - version: 7.0.1 - resolution: "pretty-ms@npm:7.0.1" - dependencies: - parse-ms: "npm:^2.1.0" - checksum: 10c0/069aec9d939e7903846b3db53b020bed92e3dc5909e0fef09ec8ab104a0b7f9a846605a1633c60af900d288582fb333f6f30469e59d6487a2330301fad35a89c - languageName: node - linkType: hard - "printj@npm:~1.3.1": version: 1.3.1 resolution: "printj@npm:1.3.1" @@ -11535,17 +10543,6 @@ __metadata: languageName: node linkType: hard -"read-pkg-up@npm:10.0.0": - version: 10.0.0 - resolution: "read-pkg-up@npm:10.0.0" - dependencies: - find-up: "npm:^6.3.0" - read-pkg: "npm:^8.0.0" - type-fest: "npm:^3.12.0" - checksum: 10c0/92579d91f1bbfd8a062d62286ea6e268a89623ad3d3fceb86f75d5b3336a1e3d0d13c4ea2b575b562086e08432d7c514c7f719be3ec402e34f3c9110deebf193 - languageName: node - linkType: hard - "read-pkg-up@npm:^3.0.0": version: 3.0.0 resolution: "read-pkg-up@npm:3.0.0" @@ -11590,18 +10587,6 @@ __metadata: languageName: node linkType: hard -"read-pkg@npm:^8.0.0": - version: 8.0.0 - resolution: "read-pkg@npm:8.0.0" - dependencies: - "@types/normalize-package-data": "npm:^2.4.1" - normalize-package-data: "npm:^5.0.0" - parse-json: "npm:^7.0.0" - type-fest: "npm:^3.8.0" - checksum: 10c0/708e3fff72e6090e1b189af22e4d00ffabde4295171a98ce910a3800dedd8384ad4cab1915283351a4e5fc884dca2e4111ddb85b28a242ffff98a2e1e1e9ca91 - languageName: node - linkType: hard - "read@npm:^2.0.0": version: 2.1.0 resolution: "read@npm:2.1.0" @@ -11668,13 +10653,6 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:^4.0.1": - version: 4.0.2 - resolution: "readdirp@npm:4.0.2" - checksum: 10c0/a16ecd8ef3286dcd90648c3b103e3826db2b766cdb4a988752c43a83f683d01c7059158d623cbcd8bdfb39e65d302d285be2d208e7d9f34d022d912b929217dd - languageName: node - linkType: hard - "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -11693,15 +10671,6 @@ __metadata: languageName: node linkType: hard -"recursive-readdir@npm:^2.2.3": - version: 2.2.3 - resolution: "recursive-readdir@npm:2.2.3" - dependencies: - minimatch: "npm:^3.0.5" - checksum: 10c0/d0238f137b03af9cd645e1e0b40ae78b6cda13846e3ca57f626fcb58a66c79ae018a10e926b13b3a460f1285acc946a4e512ea8daa2e35df4b76a105709930d1 - languageName: node - linkType: hard - "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0" @@ -11955,13 +10924,6 @@ __metadata: languageName: node linkType: hard -"run-async@npm:^3.0.0": - version: 3.0.0 - resolution: "run-async@npm:3.0.0" - checksum: 10c0/b18b562ae37c3020083dcaae29642e4cc360c824fbfb6b7d50d809a9d5227bb986152d09310255842c8dce40526e82ca768f02f00806c91ba92a8dfa6159cb85 - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.1.10 resolution: "run-parallel@npm:1.1.10" @@ -11969,7 +10931,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.5.5, rxjs@npm:^7.8.1": +"rxjs@npm:^7.5.5": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -12022,13 +10984,6 @@ __metadata: languageName: node linkType: hard -"safe-stable-stringify@npm:^2.3.1": - version: 2.4.3 - resolution: "safe-stable-stringify@npm:2.4.3" - checksum: 10c0/81dede06b8f2ae794efd868b1e281e3c9000e57b39801c6c162267eb9efda17bd7a9eafa7379e1f1cacd528d4ced7c80d7460ad26f62ada7c9e01dec61b2e768 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -12271,7 +11226,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": +"signal-exit@npm:^4.0.1": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 @@ -12487,7 +11442,7 @@ __metadata: languageName: node linkType: hard -"split2@npm:^4.1.0, split2@npm:^4.2.0": +"split2@npm:^4.2.0": version: 4.2.0 resolution: "split2@npm:4.2.0" checksum: 10c0/b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 @@ -12544,15 +11499,6 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.3": - version: 2.0.5 - resolution: "stack-utils@npm:2.0.5" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/059f828eed5b03b963e8200529c27bd92b105f2cac9dffc9edcbc739ea8fa108e4ec45d0da257d8e0f7b5ac98db5643a0787e5c25ceab1396f7123e1ee15a086 - languageName: node - linkType: hard - "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -12567,13 +11513,6 @@ __metadata: languageName: node linkType: hard -"stream-buffers@npm:^3.0.2": - version: 3.0.2 - resolution: "stream-buffers@npm:3.0.2" - checksum: 10c0/5f12f5a3af4d2012b4f5386a05667b16710fdfc3d9c9db12ec64c44d9f0f831b10cf8c941afd5398b6f47ddb3db692894a16e0f82a8a22b43a5ecb424064ce40 - languageName: node - linkType: hard - "stream-combiner@npm:~0.0.4": version: 0.0.4 resolution: "stream-combiner@npm:0.0.4" @@ -12615,13 +11554,6 @@ __metadata: languageName: node linkType: hard -"strict-event-emitter@npm:^0.1.0": - version: 0.1.0 - resolution: "strict-event-emitter@npm:0.1.0" - checksum: 10c0/94cf9f0eac5b34f11b8c40bfb7922bdbdb1e582f98ea1468c1be771a69c5f86f12ef997db234ae4bd4adcecf39024b37129f94916095112a92ec7e511803a627 - languageName: node - linkType: hard - "string-width-cjs@npm:string-width@^4.2.0": version: 4.2.0 resolution: "string-width@npm:4.2.0" @@ -12757,13 +11689,6 @@ __metadata: languageName: node linkType: hard -"strip-final-newline@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-final-newline@npm:3.0.0" - checksum: 10c0/a771a17901427bac6293fd416db7577e2bc1c34a19d38351e9d5478c3c415f523f391003b42ed475f27e33a78233035df183525395f731d3bfb8cdcbd4da08ce - languageName: node - linkType: hard - "strip-indent@npm:^3.0.0": version: 3.0.0 resolution: "strip-indent@npm:3.0.0" @@ -13039,13 +11964,6 @@ __metadata: languageName: node linkType: hard -"tinyrainbow@npm:^1.2.0": - version: 1.2.0 - resolution: "tinyrainbow@npm:1.2.0" - checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 - languageName: node - linkType: hard - "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -13115,22 +12033,6 @@ __metadata: languageName: node linkType: hard -"triple-beam@npm:^1.3.0": - version: 1.4.1 - resolution: "triple-beam@npm:1.4.1" - checksum: 10c0/4bf1db71e14fe3ff1c3adbe3c302f1fdb553b74d7591a37323a7badb32dc8e9c290738996cbb64f8b10dc5a3833645b5d8c26221aaaaa12e50d1251c9aba2fea - languageName: node - linkType: hard - -"ts-api-utils@npm:^1.3.0": - version: 1.4.3 - resolution: "ts-api-utils@npm:1.4.3" - peerDependencies: - typescript: ">=4.2.0" - checksum: 10c0/e65dc6e7e8141140c23e1dc94984bf995d4f6801919c71d6dc27cf0cd51b100a91ffcfe5217626193e5bea9d46831e8586febdc7e172df3f1091a7384299e23a - languageName: node - linkType: hard - "ts-api-utils@npm:^2.0.0": version: 2.0.0 resolution: "ts-api-utils@npm:2.0.0" @@ -13312,13 +12214,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^3.12.0, type-fest@npm:^3.8.0": - version: 3.13.1 - resolution: "type-fest@npm:3.13.1" - checksum: 10c0/547d22186f73a8c04590b70dcf63baff390078c75ea8acd366bbd510fd0646e348bd1970e47ecf795b7cff0b41d26e9c475c1fedd6ef5c45c82075fbf916b629 - languageName: node - linkType: hard - "type-fest@npm:^4.27.0": version: 4.29.0 resolution: "type-fest@npm:4.29.0" @@ -13728,15 +12623,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:9.0.1": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b - languageName: node - linkType: hard - "uuid@npm:^10.0.0": version: 10.0.0 resolution: "uuid@npm:10.0.0" @@ -13823,13 +12709,6 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:4.0.0-beta.3": - version: 4.0.0-beta.3 - resolution: "web-streams-polyfill@npm:4.0.0-beta.3" - checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e - languageName: node - linkType: hard - "web-streams-polyfill@npm:^3.0.3": version: 3.2.1 resolution: "web-streams-polyfill@npm:3.2.1" @@ -13856,7 +12735,7 @@ __metadata: languageName: node linkType: hard -"webdriverio@npm:8.41.0, webdriverio@npm:^8.29.3": +"webdriverio@npm:8.41.0": version: 8.41.0 resolution: "webdriverio@npm:8.41.0" dependencies: @@ -14098,17 +12977,6 @@ __metadata: languageName: node linkType: hard -"winston-transport@npm:^4.5.0": - version: 4.5.0 - resolution: "winston-transport@npm:4.5.0" - dependencies: - logform: "npm:^2.3.2" - readable-stream: "npm:^3.6.0" - triple-beam: "npm:^1.3.0" - checksum: 10c0/110a47c5acc87c3aa0f101741c0a992e52a86802272838c18aede8178d2b5e80254d2433dcac3439cefbc2777d9e22e65f84e9cee3130681c58e4ae5d58f50c3 - languageName: node - linkType: hard - "wordwrap@npm:^1.0.0": version: 1.0.0 resolution: "wordwrap@npm:1.0.0" @@ -14127,17 +12995,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^6.2.0": - version: 6.2.0 - resolution: "wrap-ansi@npm:6.2.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c - languageName: node - linkType: hard - "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -14239,13 +13096,6 @@ __metadata: languageName: node linkType: hard -"xmlbuilder@npm:^15.1.1": - version: 15.1.1 - resolution: "xmlbuilder@npm:15.1.1" - checksum: 10c0/665266a8916498ff8d82b3d46d3993913477a254b98149ff7cff060d9b7cc0db7cf5a3dae99aed92355254a808c0e2e3ec74ad1b04aa1061bdb8dfbea26c18b8 - languageName: node - linkType: hard - "xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" @@ -14321,16 +13171,6 @@ __metadata: languageName: node linkType: hard -"yauzl@npm:^3.0.0": - version: 3.1.0 - resolution: "yauzl@npm:3.1.0" - dependencies: - buffer-crc32: "npm:~0.2.3" - pend: "npm:~1.2.0" - checksum: 10c0/610c23ba125c4c838ea563a31efa8df1e370967ff4de11d47dbce1056e62429ce95ce8a9d53ccb35fd1690e594ca4dcb3bd7dbf5e0e9311c250efde33a6653b2 - languageName: node - linkType: hard - "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" @@ -14345,13 +13185,6 @@ __metadata: languageName: node linkType: hard -"yocto-queue@npm:^1.0.0": - version: 1.0.0 - resolution: "yocto-queue@npm:1.0.0" - checksum: 10c0/856117aa15cf5103d2a2fb173f0ab4acb12b4b4d0ed3ab249fdbbf612e55d1cadfd27a6110940e24746fb0a78cf640b522cc8bca76f30a3b00b66e90cf82abe0 - languageName: node - linkType: hard - "zip-stream@npm:^6.0.1": version: 6.0.1 resolution: "zip-stream@npm:6.0.1" From 28e15b575a9f98c14a7a6553d6c05f385af8a8ac Mon Sep 17 00:00:00 2001 From: Nicolas Ulrich Date: Mon, 27 Jan 2025 17:19:10 +0100 Subject: [PATCH 27/68] =?UTF-8?q?=E2=9C=85=20session=20scenario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/helpers/browser.ts | 10 +- test/e2e/lib/helpers/session.ts | 25 ++--- test/e2e/scenario/rum/sessions.scenario.ts | 96 ++++++++++--------- test/e2e/scenario/trackingConsent.scenario.ts | 18 +--- 4 files changed, 70 insertions(+), 79 deletions(-) diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 62005578f9..e81111e985 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -88,16 +88,14 @@ export function deleteAllCookies(context: BrowserContext) { return context.clearCookies() } -export function setCookie(name: string, value: string, expiresDelay: number = 0) { - return browser.execute( - (name, value, expiresDelay) => { +export function setCookie(page: Page, name: string, value: string, expiresDelay: number = 0) { + return page.evaluate( + ({ name, value, expiresDelay }: { name: string; value: string; expiresDelay: number }) => { const expires = new Date(Date.now() + expiresDelay).toUTCString() document.cookie = `${name}=${value};expires=${expires};` }, - name, - value, - expiresDelay + { name, value, expiresDelay } ) } diff --git a/test/e2e/lib/helpers/session.ts b/test/e2e/lib/helpers/session.ts index ada8a60a0a..3d978b98f7 100644 --- a/test/e2e/lib/helpers/session.ts +++ b/test/e2e/lib/helpers/session.ts @@ -1,31 +1,34 @@ import { SESSION_STORE_KEY, SESSION_TIME_OUT_DELAY } from '@datadog/browser-core' import type { SessionState } from '@datadog/browser-core' +import type { BrowserContext, Page } from '@playwright/test' +import { expect } from '@playwright/test' + import { setCookie } from './browser' -export async function renewSession() { - await expireSession() - const documentElement = await $('html') +export async function renewSession(page: Page, browserContext: BrowserContext) { + await expireSession(page, browserContext) + const documentElement = page.locator('html') await documentElement.click() - expect((await findSessionCookie())?.isExpired).not.toEqual('1') + expect((await findSessionCookie(browserContext))?.isExpired).not.toEqual('1') } -export async function expireSession() { +export async function expireSession(page: Page, browserContext: BrowserContext) { // mock expire session with anonymous id - const cookies = await browser.getCookies(SESSION_STORE_KEY) + const cookies = await browserContext.cookies() const anonymousId = cookies[0]?.value.match(/aid=[a-z0-9]+/) const expireCookie = `isExpired=1&${anonymousId && anonymousId[0]}` - await setCookie(SESSION_STORE_KEY, expireCookie, SESSION_TIME_OUT_DELAY) + await setCookie(page, SESSION_STORE_KEY, expireCookie, SESSION_TIME_OUT_DELAY) - expect((await findSessionCookie())?.isExpired).toEqual('1') + expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') // Cookies are cached for 1s, wait until the cache expires - await browser.pause(1100) + await page.waitForTimeout(1100) } -export async function findSessionCookie() { - const cookies = await browser.getCookies(SESSION_STORE_KEY) +export async function findSessionCookie(browserContext: BrowserContext) { + const cookies = await browserContext.cookies() // In some case, the session cookie is returned but with an empty value. Let's consider it expired // in this case. const rawValue = cookies[0]?.value diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 8efe06d563..0b30bce66a 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -1,14 +1,14 @@ import { RecordType } from '@datadog/browser-rum/src/types' +import { test, expect } from '@playwright/test' import { expireSession, findSessionCookie, renewSession } from '../../lib/helpers/session' -import { bundleSetup, createTest, flushEvents, waitForRequests } from '../../lib/framework' -import { deleteAllCookies, sendXhr } from '../../lib/helpers/browser' +import { bundleSetup, createTest, waitForRequests } from '../../lib/framework' -describe('rum sessions', () => { - describe('session renewal', () => { +test.describe('rum sessions', () => { + test.describe('session renewal', () => { createTest('create a new View when the session is renewed') .withRum() - .run(async ({ intakeRegistry }) => { - await renewSession() + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await renewSession(page, browserContext) await flushEvents() const viewEvents = intakeRegistry.rumViewEvents const firstViewEvent = viewEvents[0] @@ -23,8 +23,8 @@ describe('rum sessions', () => { createTest('a single fullSnapshot is taken when the session is renewed') .withRum() .withSetup(bundleSetup) - .run(async ({ intakeRegistry }) => { - await renewSession() + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await renewSession(page, browserContext) await flushEvents() @@ -39,62 +39,64 @@ describe('rum sessions', () => { }) }) - describe('session expiration', () => { + test.describe('session expiration', () => { createTest("don't send events when session is expired") // prevent recording start to generate late events .withRum({ startSessionReplayRecordingManually: true }) - .run(async ({ intakeRegistry }) => { - await expireSession() + .run(async ({ intakeRegistry, sendXhr, browserContext, page }) => { + await expireSession(page, browserContext) intakeRegistry.empty() await sendXhr('/ok') expect(intakeRegistry.isEmpty).toBe(true) }) }) - describe('anonymous user id', () => { + test.describe('anonymous user id', () => { createTest('persists when session is expired') .withRum() - .run(async () => { - const anonymousId = (await findSessionCookie())?.aid + .run(async ({ flushEvents, browserContext, page }) => { + const anonymousId = (await findSessionCookie(browserContext))?.aid - await expireSession() + await expireSession(page, browserContext) await flushEvents() - expect((await findSessionCookie())?.aid).toEqual(anonymousId) + expect((await findSessionCookie(browserContext))?.aid).toEqual(anonymousId) }) createTest('persists when session renewed') .withRum() - .run(async () => { - const anonymousId = (await findSessionCookie())?.aid + .run(async ({ browserContext, page }) => { + const anonymousId = (await findSessionCookie(browserContext))?.aid expect(anonymousId).not.toBeNull() - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.stopSession() }) - await (await $('html')).click() + await page.locator('html').click() // The session is not created right away, let's wait until we see a cookie - await browser.waitUntil(async () => Boolean(await findSessionCookie())) + await page.waitForTimeout(1000) - expect((await findSessionCookie())?.aid).toEqual(anonymousId) + expect((await findSessionCookie(browserContext))?.aid).toEqual(anonymousId) + + expect(true).toBeTruthy() }) createTest('generated when cookie is cleared') .withRum() - .run(async () => { + .run(async ({ deleteAllCookies, flushEvents, browserContext, page }) => { await deleteAllCookies() - await renewSession() + await renewSession(page, browserContext) await flushEvents() - expect((await findSessionCookie())?.aid).toBeDefined() + expect((await findSessionCookie(browserContext))?.aid).toBeDefined() }) }) - describe('manual session expiration', () => { + test.describe('manual session expiration', () => { createTest('calling stopSession() stops the session') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.executeAsync((done) => { + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await page.evaluate(() => { window.DD_RUM!.stopSession() setTimeout(() => { // If called directly after `stopSession`, the action start time may be the same as the @@ -103,51 +105,51 @@ describe('rum sessions', () => { // We might want to improve this by having a strict comparison between the event start // time and session end time. window.DD_RUM!.addAction('foo') - done() + // done() }, 5) }) await flushEvents() - expect((await findSessionCookie())?.isExpired).toEqual('1') + expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') expect(intakeRegistry.rumActionEvents.length).toBe(0) }) createTest('after calling stopSession(), a user interaction starts a new session') .withRum() - .run(async ({ intakeRegistry }) => { - await browser.execute(() => { + .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { + await page.evaluate(() => { window.DD_RUM!.stopSession() }) - await (await $('html')).click() + await page.locator('html').click() // The session is not created right away, let's wait until we see a cookie - await browser.waitUntil(async () => Boolean(await findSessionCookie())) + await page.waitForTimeout(1000) - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.addAction('foo') }) await flushEvents() - expect((await findSessionCookie())?.isExpired).not.toEqual('1') - expect((await findSessionCookie())?.id).toBeDefined() + expect((await findSessionCookie(browserContext))?.isExpired).not.toEqual('1') + expect((await findSessionCookie(browserContext))?.id).toBeDefined() expect(intakeRegistry.rumActionEvents.length).toBe(1) }) createTest('flush events when the session expires') .withRum() .withLogs() - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, page }) => { expect(intakeRegistry.rumViewEvents.length).toBe(0) expect(intakeRegistry.logsEvents.length).toBe(0) expect(intakeRegistry.replaySegments.length).toBe(0) - await browser.execute(() => { + await page.evaluate(() => { window.DD_LOGS!.logger.log('foo') window.DD_RUM!.stopSession() }) - await waitForRequests() + await waitForRequests(page) expect(intakeRegistry.rumViewEvents.length).toBe(1) expect(intakeRegistry.rumViewEvents[0].session.is_active).toBe(false) @@ -156,26 +158,26 @@ describe('rum sessions', () => { }) }) - describe('third party cookie clearing', () => { + test.describe('third party cookie clearing', () => { createTest('after a 3rd party clears the cookies, do not restart a session on user interaction') .withRum() - .run(async ({ intakeRegistry }) => { + .run(async ({ intakeRegistry, deleteAllCookies, flushEvents, browserContext, page }) => { await deleteAllCookies() // Cookies are cached for 1s, wait until the cache expires - await browser.pause(1100) + await page.waitForTimeout(1100) - await (await $('html')).click() + await page.locator('html').click() - await browser.pause(1100) + await page.waitForTimeout(1100) - await browser.execute(() => { + await page.evaluate(() => { window.DD_RUM!.addAction('foo') }) await flushEvents() - expect(await findSessionCookie()).toBeUndefined() + expect(await findSessionCookie(browserContext)).toBeUndefined() expect(intakeRegistry.rumActionEvents.length).toBe(0) expect(intakeRegistry.rumViewEvents.length).toBe(1) expect(intakeRegistry.rumViewEvents[0].session.is_active).toBe(false) diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index 7af00d5596..7ec729a669 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -1,6 +1,6 @@ -import { SessionState } from '@datadog/browser-core' -import { createTest, flushEvents } from '../lib/framework' -import { test, expect, BrowserContext } from '@playwright/test' +import { test, expect } from '@playwright/test' +import { findSessionCookie } from 'lib/helpers/session' +import { createTest } from '../lib/framework' test.describe('tracking consent', () => { test.describe('RUM', () => { @@ -99,15 +99,3 @@ test.describe('tracking consent', () => { }) }) }) - -// TODO: use lib/helper/session when sessions.scenario is migrated -export async function findSessionCookie(browserContext: BrowserContext) { - const cookies = await browserContext.cookies() - // In some case, the session cookie is returned but with an empty value. Let's consider it expired - // in this case. - const rawValue = cookies[0]?.value - if (!rawValue) { - return - } - return Object.fromEntries(rawValue.split('&').map((part: string) => part.split('='))) as SessionState -} From 80fdbc35be89f8eae92a5b005fb8fd00c13f733d Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:15:38 +0100 Subject: [PATCH 28/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20linting=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 3 +- test/e2e/lib/framework/logger.ts | 2 +- test/e2e/lib/helpers/browser.ts | 114 +++++++++--------- .../{notice-reporter.ts => noticeReporter.ts} | 3 +- test/e2e/playwright.base.config.ts | 3 +- test/e2e/playwright.local.config.ts | 3 +- .../developerExtension.scenario.ts | 9 +- test/e2e/scenario/logs.scenario.ts | 4 +- .../scenario/recorder/viewports.scenario.ts | 2 +- test/e2e/scenario/rum/errors.scenario.ts | 27 ++--- test/e2e/scenario/rum/s8sInject.scenario.ts | 4 +- test/e2e/scenario/sessionStore.scenario.ts | 5 +- test/e2e/scenario/telemetry.scenario.ts | 2 +- test/e2e/scenario/trackingConsent.scenario.ts | 2 +- test/e2e/tsconfig.json | 2 +- test/e2e/wdio.base.conf.ts | 89 -------------- test/e2e/wdio.bs.conf.ts | 44 ------- test/e2e/wdio.developer-extension.conf.ts | 26 ---- test/e2e/wdio.local.conf.ts | 15 --- 19 files changed, 94 insertions(+), 265 deletions(-) rename test/e2e/{notice-reporter.ts => noticeReporter.ts} (81%) delete mode 100644 test/e2e/wdio.base.conf.ts delete mode 100644 test/e2e/wdio.bs.conf.ts delete mode 100644 test/e2e/wdio.developer-extension.conf.ts delete mode 100644 test/e2e/wdio.local.conf.ts diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 161799aabe..3bba4769f0 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -4,8 +4,7 @@ import { DefaultPrivacyLevel } from '@datadog/browser-rum' import type { BrowserContext, Page } from '@playwright/test' import { test, expect } from '@playwright/test' import { getRunId } from '../../../envUtils' -import { BrowserLog } from '../helpers/browser' -import { BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' +import type { BrowserLog, BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import { IntakeRegistry } from './intakeRegistry' diff --git a/test/e2e/lib/framework/logger.ts b/test/e2e/lib/framework/logger.ts index f2b007bc78..d82fa34a52 100644 --- a/test/e2e/lib/framework/logger.ts +++ b/test/e2e/lib/framework/logger.ts @@ -4,7 +4,7 @@ import { inspect } from 'util' const logsPath = undefined // (browser.options as WebdriverIO.Config & { logsPath: string }).logsPath const stream: { write(s: string): void } = logsPath ? fs.createWriteStream(logsPath, { flags: 'a' }) - : { write: () => {} } // TODO: why do we need to log to stdout? + : { write: () => undefined } // TODO: why do we need to log to stdout? export function log(...args: any[]) { const prefix = `[${process.pid}] ${new Date().toISOString()}` diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index e81111e985..33bec3fec7 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -1,63 +1,63 @@ -import * as os from 'os' +// import * as os from 'os' +// import { url } from 'inspector' +// import { resolve } from 'dns' import type { BrowserContext, Page } from '@playwright/test' -import { url } from 'inspector' -import { resolve } from 'dns' // To keep tests sane, ensure we got a fixed list of possible platforms and browser names. -const validPlatformNames = ['windows', 'macos', 'linux', 'ios', 'android'] as const -const validBrowserNames = ['edge', 'safari', 'chrome', 'firefox'] as const - -export function getBrowserName(): (typeof validBrowserNames)[number] { - const capabilities = browser.capabilities - - // Look for the browser name in capabilities. It should always be there as long as we don't change - // the browser capabilities format. - if (!('browserName' in capabilities) || typeof capabilities.browserName !== 'string') { - throw new Error("Can't get browser name (no browser name)") - } - let browserName = capabilities.browserName.toLowerCase() - if (browserName === 'msedge') { - browserName = 'edge' - } else if (browserName === 'chrome-headless-shell') { - browserName = 'chrome' - } - if (!includes(validBrowserNames, browserName)) { - throw new Error(`Can't get browser name (invalid browser name ${browserName})`) - } - - return browserName -} - -export function getPlatformName(): (typeof validPlatformNames)[number] { - const capabilities = browser.capabilities - - let platformName: string - if ('bstack:options' in capabilities && capabilities['bstack:options']) { - // Look for the platform name in browserstack options. It might not be always there, for example - // when we run the test locally. This should be adjusted when we are changing the browser - // capabilities format. - platformName = (capabilities['bstack:options'] as any).os - } else { - // The test is run locally, use the local os name - platformName = os.type() - } - - platformName = platformName.toLowerCase() - if (/^(mac ?os|os ?x|mac ?os ?x|darwin)$/.test(platformName)) { - platformName = 'macos' - } else if (platformName === 'windows_nt') { - platformName = 'windows' - } - if (!includes(validPlatformNames, platformName)) { - throw new Error(`Can't get platform name (invalid platform name ${platformName})`) - } - - return platformName -} - -function includes(list: readonly T[], item: unknown): item is T { - return list.includes(item as any) -} +// const validPlatformNames = ['windows', 'macos', 'linux', 'ios', 'android'] as const +// const validBrowserNames = ['edge', 'safari', 'chrome', 'firefox'] as const + +// export function getBrowserName(): (typeof validBrowserNames)[number] { +// const capabilities = browser.capabilities + +// // Look for the browser name in capabilities. It should always be there as long as we don't change +// // the browser capabilities format. +// if (!('browserName' in capabilities) || typeof capabilities.browserName !== 'string') { +// throw new Error("Can't get browser name (no browser name)") +// } +// let browserName = capabilities.browserName.toLowerCase() +// if (browserName === 'msedge') { +// browserName = 'edge' +// } else if (browserName === 'chrome-headless-shell') { +// browserName = 'chrome' +// } +// if (!includes(validBrowserNames, browserName)) { +// throw new Error(`Can't get browser name (invalid browser name ${browserName})`) +// } + +// return browserName +// } + +// export function getPlatformName(): (typeof validPlatformNames)[number] { +// const capabilities = browser.capabilities + +// let platformName: string +// if ('bstack:options' in capabilities && capabilities['bstack:options']) { +// // Look for the platform name in browserstack options. It might not be always there, for example +// // when we run the test locally. This should be adjusted when we are changing the browser +// // capabilities format. +// platformName = (capabilities['bstack:options'] as any).os +// } else { +// // The test is run locally, use the local os name +// platformName = os.type() +// } + +// platformName = platformName.toLowerCase() +// if (/^(mac ?os|os ?x|mac ?os ?x|darwin)$/.test(platformName)) { +// platformName = 'macos' +// } else if (platformName === 'windows_nt') { +// platformName = 'windows' +// } +// if (!includes(validPlatformNames, platformName)) { +// throw new Error(`Can't get platform name (invalid platform name ${platformName})`) +// } + +// return platformName +// } + +// function includes(list: readonly T[], item: unknown): item is T { +// return list.includes(item as any) +// } export interface BrowserLog { level: 'log' | 'debug' | 'info' | 'error' | 'warning' diff --git a/test/e2e/notice-reporter.ts b/test/e2e/noticeReporter.ts similarity index 81% rename from test/e2e/notice-reporter.ts rename to test/e2e/noticeReporter.ts index ff6dc1a4dd..abe13cd0ac 100644 --- a/test/e2e/notice-reporter.ts +++ b/test/e2e/noticeReporter.ts @@ -1,7 +1,8 @@ import type { Reporter } from '@playwright/test/reporter' -import { APPLICATION_ID } from 'lib/helpers/configuration' import { getRunId } from '../envUtils' +import { APPLICATION_ID } from './lib/helpers/configuration' +// eslint-disable-next-line import/no-default-export export default class NoticeReporter implements Reporter { onBegin() { console.log( diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 9c4a2c2164..b7e7b9a2e8 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -1,4 +1,4 @@ -import { ReporterDescription, Config } from '@playwright/test' +import type { ReporterDescription, Config } from '@playwright/test' import { getTestReportDirectory } from '../envUtils' const reporters: ReporterDescription[] = [['line'], ['./notice-reporter.ts']] @@ -21,7 +21,6 @@ if (testReportDirectory) { export const config: Config = { testDir: './scenario', testMatch: ['**/*.scenario.ts'], - testIgnore: ['**/sessions.scenario.ts'], fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, diff --git a/test/e2e/playwright.local.config.ts b/test/e2e/playwright.local.config.ts index 04fa8d6d51..ee1d1a45bc 100644 --- a/test/e2e/playwright.local.config.ts +++ b/test/e2e/playwright.local.config.ts @@ -1,6 +1,7 @@ -import { config as baseConfig } from './playwright.base.config' import { defineConfig, devices } from '@playwright/test' +import { config as baseConfig } from './playwright.base.config' +// eslint-disable-next-line import/no-default-export export default defineConfig({ ...baseConfig, projects: [ diff --git a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts index 9af5076501..eb48a4ea86 100644 --- a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts +++ b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts @@ -1,12 +1,13 @@ -import { test as base, chromium, Page, type BrowserContext, expect } from '@playwright/test' import path from 'path' +import { test as base, chromium, expect } from '@playwright/test' +import type { Page, BrowserContext } from '@playwright/test' const test = base.extend<{ context: BrowserContext extensionId: string developerExtension: DeveloperExtensionPage }>({ - context: async ({}, use) => { + context: async (_fixture, use) => { const pathToExtension = path.join(process.cwd(), 'developer-extension', 'dist') const context = await chromium.launchPersistentContext('', { @@ -18,7 +19,9 @@ const test = base.extend<{ }, extensionId: async ({ context }, use) => { let [background] = context.serviceWorkers() - if (!background) background = await context.waitForEvent('serviceworker') + if (!background) { + background = await context.waitForEvent('serviceworker') + } const extensionId = background.url().split('/')[2] await use(extensionId) diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index 6e475aa987..bb6662fd1b 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -79,7 +79,7 @@ test.describe('logs', () => { createTest('send fetch network errors') .withLogs({ forwardErrorsToLogs: true }) .run(async ({ intakeRegistry, flushEvents, page, withBrowserLogs }) => { - await page.evaluate((unreachableUrl) => fetch(unreachableUrl).catch(() => {}), UNREACHABLE_URL) + await page.evaluate((unreachableUrl) => fetch(unreachableUrl).catch(() => undefined), UNREACHABLE_URL) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) @@ -97,7 +97,7 @@ test.describe('logs', () => { createTest('keep only the first bytes of the response') .withLogs({ forwardErrorsToLogs: true }) .run(async ({ intakeRegistry, baseUrl, servers, flushEvents, page, withBrowserLogs }) => { - await page.evaluate(() => fetch('/throw-large-response').then(() => {}, console.log)) + await page.evaluate(() => fetch('/throw-large-response').then(() => undefined, console.log)) await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) diff --git a/test/e2e/scenario/recorder/viewports.scenario.ts b/test/e2e/scenario/recorder/viewports.scenario.ts index 74577cb928..7c056107b0 100644 --- a/test/e2e/scenario/recorder/viewports.scenario.ts +++ b/test/e2e/scenario/recorder/viewports.scenario.ts @@ -45,7 +45,7 @@ test.describe('recorder', () => { findAllIncrementalSnapshots(segment, IncrementalSource.ViewportResize) ).data as ViewportResizeData - const scrollbarThicknessCorrection = await getScrollbarThicknessCorrection(page) + const scrollbarThicknessCorrection = getScrollbarThicknessCorrection(page) expectToBeNearby(lastViewportResizeData.width, innerWidth - scrollbarThicknessCorrection) expectToBeNearby(lastViewportResizeData.height, innerHeight - scrollbarThicknessCorrection) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 56644c66de..87cf12b27e 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -1,7 +1,6 @@ import type { RumErrorEvent } from '@datadog/browser-rum-core' -import { createTest, html } from '../../lib/framework' -import { getBrowserName, getPlatformName } from '../../lib/helpers/browser' import { test, expect } from '@playwright/test' +import { createTest, html } from '../../lib/framework' // Note: using `browser.execute` to throw exceptions may result in "Script error." being reported, // because WDIO is evaluating the script in a different context than the page. @@ -25,7 +24,7 @@ test.describe('rum errors', () => { .withRum() .withBody(createBody('console.error("oh snap")')) .run(async ({ page, intakeRegistry, baseUrl, flushEvents, withBrowserLogs }) => { - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -36,7 +35,7 @@ test.describe('rum errors', () => { handlingStack: ['Error: ', `handler @ ${baseUrl}/:`], handling: 'handled', }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) @@ -45,7 +44,7 @@ test.describe('rum errors', () => { .withRum() .withBody(createBody('console.error("Foo:", foo())')) .run(async ({ page, flushEvents, intakeRegistry, baseUrl, withBrowserLogs }) => { - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -57,7 +56,7 @@ test.describe('rum errors', () => { handlingStack: ['Error: ', `handler @ ${baseUrl}/:`], handling: 'handled', }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) @@ -66,7 +65,7 @@ test.describe('rum errors', () => { .withRum() .withBody(createBody('throw foo()')) .run(async ({ page, flushEvents, intakeRegistry, baseUrl, withBrowserLogs }) => { - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -77,7 +76,7 @@ test.describe('rum errors', () => { stack: ['Error: oh snap', `at foo @ ${baseUrl}/:`, `handler @ ${baseUrl}/:`], handling: 'unhandled', }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) @@ -86,7 +85,7 @@ test.describe('rum errors', () => { .withRum() .withBody(createBody('Promise.reject(foo())')) .run(async ({ flushEvents, page, intakeRegistry, baseUrl, withBrowserLogs }) => { - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -97,7 +96,7 @@ test.describe('rum errors', () => { stack: ['Error: oh snap', `at foo @ ${baseUrl}/:`, `handler @ ${baseUrl}/:`], handling: 'unhandled', }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) @@ -106,7 +105,7 @@ test.describe('rum errors', () => { .withRum() .withBody(createBody('DD_RUM.addError(foo())')) .run(async ({ flushEvents, page, intakeRegistry, baseUrl, withBrowserLogs }) => { - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -118,7 +117,7 @@ test.describe('rum errors', () => { handlingStack: ['Error: ', `handler @ ${baseUrl}/:`], handling: 'handled', }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(0) }) }) @@ -140,7 +139,7 @@ test.describe('rum errors', () => { const userAgent = await page.evaluate(() => navigator.userAgent) test.skip(browserName === 'firefox' || (browserName === 'webkit' && userAgent.includes('Mac OS X'))) - const button = await page.locator('button') + const button = page.locator('button') await button.click() await flushEvents() @@ -158,7 +157,7 @@ test.describe('rum errors', () => { disposition: 'enforce', }, }) - await withBrowserLogs((browserLogs) => { + withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) }) diff --git a/test/e2e/scenario/rum/s8sInject.scenario.ts b/test/e2e/scenario/rum/s8sInject.scenario.ts index baa878de87..54b6f0daca 100644 --- a/test/e2e/scenario/rum/s8sInject.scenario.ts +++ b/test/e2e/scenario/rum/s8sInject.scenario.ts @@ -1,8 +1,8 @@ import * as fs from 'fs' -import { RUM_BUNDLE } from '../../lib/framework' -import { APPLICATION_ID, CLIENT_TOKEN } from '../../lib/helpers/configuration' import puppeteer from 'puppeteer' import { test, expect } from '@playwright/test' +import { RUM_BUNDLE } from '../../lib/framework' +import { APPLICATION_ID, CLIENT_TOKEN } from '../../lib/helpers/configuration' test.describe('Inject RUM with Puppeteer', () => { // S8s tests inject RUM with puppeteer evaluateOnNewDocument diff --git a/test/e2e/scenario/sessionStore.scenario.ts b/test/e2e/scenario/sessionStore.scenario.ts index c011729082..d7ae87bf27 100644 --- a/test/e2e/scenario/sessionStore.scenario.ts +++ b/test/e2e/scenario/sessionStore.scenario.ts @@ -1,6 +1,7 @@ import { SESSION_STORE_KEY } from '@datadog/browser-core' +import type { BrowserContext, Page } from '@playwright/test' +import { test, expect } from '@playwright/test' import { createTest } from '../lib/framework' -import { test, expect, BrowserContext, Page } from '@playwright/test' const DISABLE_LOCAL_STORAGE = '' const DISABLE_COOKIES = '' @@ -24,7 +25,7 @@ test.describe('Session Stores', () => { .withLogs() .withRum() .withHead(DISABLE_COOKIES) - .run(async ({ browserContext, page }) => { + .run(async ({ page }) => { const logsContext = await page.evaluate(() => window.DD_LOGS?.getInternalContext()) const rumContext = await page.evaluate(() => window.DD_RUM?.getInternalContext()) diff --git a/test/e2e/scenario/telemetry.scenario.ts b/test/e2e/scenario/telemetry.scenario.ts index eeeb530c0c..fd8a1f46a6 100644 --- a/test/e2e/scenario/telemetry.scenario.ts +++ b/test/e2e/scenario/telemetry.scenario.ts @@ -1,5 +1,5 @@ -import { bundleSetup, createTest, flushEvents } from '../lib/framework' import { test, expect } from '@playwright/test' +import { bundleSetup, createTest } from '../lib/framework' test.describe('telemetry', () => { createTest('send errors for logs') diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index 7ec729a669..b304bb4a25 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test' -import { findSessionCookie } from 'lib/helpers/session' +import { findSessionCookie } from '../lib/helpers/session' import { createTest } from '../lib/framework' test.describe('tracking consent', () => { diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index e25079306a..9ba040652f 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -8,7 +8,7 @@ "moduleResolution": "node", "resolveJsonModule": true, "target": "ES2015", - "types": ["node", "jasmine", "@wdio/globals/types", "ajv"], + "types": ["node", "ajv"], "allowJs": true }, "ts-node": { diff --git a/test/e2e/wdio.base.conf.ts b/test/e2e/wdio.base.conf.ts deleted file mode 100644 index 2c387bc140..0000000000 --- a/test/e2e/wdio.base.conf.ts +++ /dev/null @@ -1,89 +0,0 @@ -import path from 'path' -import { unlinkSync, mkdirSync } from 'fs' -import type { Options, Reporters } from '@wdio/types' -import { browser, $, $$ } from '@wdio/globals' -import { getRunId, getTestReportDirectory } from '../envUtils' -import { APPLICATION_ID } from './lib/helpers/configuration' - -const reporters: Reporters.ReporterEntry[] = [['spec', { onlyFailures: true }]] -let logsPath: string | undefined - -const testReportDirectory = getTestReportDirectory() -if (testReportDirectory) { - reporters.push([ - 'junit', - { - outputDir: testReportDirectory, - outputFileFormat(options) { - const browserName = - 'browserName' in options.capabilities && typeof options.capabilities.browserName === 'string' - ? options.capabilities.browserName - : 'unknown' - return `results-${options.cid}.${browserName}.xml` - }, - }, - ]) - logsPath = path.join(testReportDirectory, 'specs.log') -} else if (!process.env.LOGS_STDOUT) { - logsPath = 'specs.log' -} - -type OptionsWithLogsPath = Options.Testrunner & { logsPath?: string } -export const config: OptionsWithLogsPath = { - runner: 'local', - autoCompileOpts: { - autoCompile: true, - tsNodeOpts: { - project: './tsconfig.json', - }, - }, - // We do not inject @wdio globals to keep Jasmine's expect - injectGlobals: false, - specs: ['./scenario/**/*.scenario.ts'], - exclude: ['./scenario/developer-extension/*.scenario.ts'], - capabilities: [], - maxInstances: 5, - logLevel: 'warn', - bail: 0, - waitforTimeout: 10000, - connectionRetryTimeout: 90000, - connectionRetryCount: 0, - framework: 'jasmine', - reporters, - jasmineOpts: { - defaultTimeoutInterval: 60000, - }, - onPrepare: (_config, _capabilities) => { - console.log( - `[RUM events] https://app.datadoghq.com/rum/explorer?query=${encodeURIComponent( - `@application.id:${APPLICATION_ID} @context.run_id:"${getRunId()}"` - )}` - ) - console.log(`[Log events] https://app.datadoghq.com/logs?query=${encodeURIComponent(`@run_id:"${getRunId()}"`)}\n`) - - if (testReportDirectory) { - try { - mkdirSync(testReportDirectory, { recursive: true }) - } catch (e) { - console.log(`Failed to create the test report directory: ${(e as Error).message}`) - } - } - - if (logsPath) { - try { - unlinkSync(logsPath) - } catch (e) { - if ((e as NodeJS.ErrnoException).code !== 'ENOENT') { - console.log(`Failed to remove previous logs: ${(e as Error).message}`) - } - } - } - }, - beforeSession: (_config, _capabilities, _specs, _cid) => { - // Expose everything besides expect, as we want to keep the one from Jasmine - global.browser = browser - global.$ = $ - global.$$ = $$ - }, - logsPath, -} diff --git a/test/e2e/wdio.bs.conf.ts b/test/e2e/wdio.bs.conf.ts deleted file mode 100644 index 2f598fe3c5..0000000000 --- a/test/e2e/wdio.bs.conf.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Options } from '@wdio/types' -import { getBuildInfos } from '../envUtils' -import { browserConfigurations } from './browsers.conf' -import { config as baseConfig } from './wdio.base.conf' - -export const config: Options.Testrunner = { - ...baseConfig, - - specFileRetries: 1, - exclude: [...baseConfig.exclude!], - capabilities: browserConfigurations.map((configuration) => - // See https://www.browserstack.com/automate/capabilities?tag=selenium-4 - // Make sure to look at the "W3C Protocol" tab - ({ - browserName: configuration.name, - browserVersion: configuration.version, - 'bstack:options': { - os: configuration.os, - osVersion: configuration.osVersion, - deviceName: configuration.device, - - appiumVersion: '1.22.0', - seleniumVersion: '4.1.2', - - sessionName: configuration.sessionName, - projectName: 'browser sdk e2e', - buildName: getBuildInfos(), - }, - }) - ), - logLevels: { - '@wdio/browserstack-service': 'info', - }, - services: [ - [ - 'browserstack', - { - browserstackLocal: true, - }, - ], - ], - user: process.env.BS_USERNAME, - key: process.env.BS_ACCESS_KEY, -} diff --git a/test/e2e/wdio.developer-extension.conf.ts b/test/e2e/wdio.developer-extension.conf.ts deleted file mode 100644 index fd822dad69..0000000000 --- a/test/e2e/wdio.developer-extension.conf.ts +++ /dev/null @@ -1,26 +0,0 @@ -import path from 'path' -import type { Options } from '@wdio/types' -import { config as baseConfig } from './wdio.base.conf' - -export const config: Options.Testrunner = { - ...baseConfig, - - specs: ['./scenario/developer-extension/*.scenario.ts'], - exclude: [], - - capabilities: [ - { - browserName: 'chrome', - 'goog:chromeOptions': { - args: [ - `--load-extension=${path.join(process.cwd(), 'developer-extension', 'dist')}`, - '--headless=new', // "new" headless needed for extensions https://www.selenium.dev/blog/2023/headless-is-going-away/ - '--no-sandbox', - ], - }, - }, - ], - - // eslint-disable-next-line @typescript-eslint/no-empty-function - onPrepare() {}, -} diff --git a/test/e2e/wdio.local.conf.ts b/test/e2e/wdio.local.conf.ts deleted file mode 100644 index 770b45f8ad..0000000000 --- a/test/e2e/wdio.local.conf.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Options } from '@wdio/types' -import { config as baseConfig } from './wdio.base.conf' - -export const config: Options.Testrunner = { - ...baseConfig, - - capabilities: [ - { - browserName: 'chrome', - 'goog:chromeOptions': { - args: ['--headless', '--no-sandbox'], - }, - }, - ], -} From da8908fb4d55ab64fc55c5cd90a3034802167a70 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:16:12 +0100 Subject: [PATCH 29/68] =?UTF-8?q?=F0=9F=91=B7=20simplify=20yarn:test:e2e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 4 +++- package.json | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 937b54bcf3..2ddb134128 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -225,7 +225,9 @@ e2e: junit: test-report/e2e/*.xml script: - yarn - - yarn playwright install --with-deps + - yarn build + - yarn build:app + - yarn playwright install chromium --with-deps - FORCE_COLOR=1 yarn test:e2e after_script: - node ./scripts/test/export-test-result.js e2e diff --git a/package.json b/package.json index 9b157bab57..372a8d2894 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "postinstall": "scripts/cli init_submodule", "build": "lerna run build --stream", "build:bundle": "lerna run build:bundle --stream", + "build:app": "cd test/app && rm -rf node_modules && yarn && yarn build", "format": "prettier --check .", "lint": "scripts/cli lint .", "typecheck": "scripts/cli typecheck . && scripts/cli typecheck developer-extension", @@ -22,7 +23,7 @@ "test:script": "node --test --experimental-test-module-mocks './scripts/**/*.spec.*'", "test:unit:watch": "yarn test:unit --no-single-run", "test:unit:bs": "node ./scripts/test/bs-wrapper.js karma start test/unit/karma.bs.conf.js", - "test:e2e": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && playwright test --config test/e2e/playwright.local.config.ts", + "test:e2e": "playwright test --config test/e2e/playwright.local.config.ts", "test:e2e:bs": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && node ./scripts/test/bs-wrapper.js wdio test/e2e/wdio.bs.conf.ts", "test:e2e:developer-extension": "yarn build && wdio test/e2e/wdio.developer-extension.conf.ts", "test:compat:tsc": "scripts/cli check_typescript_compatibility", From 9c56010b11906fea694935cee5368623a835ea59 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:17:00 +0100 Subject: [PATCH 30/68] =?UTF-8?q?fixup!=20=F0=9F=91=8C=20fix=20linting=20e?= =?UTF-8?q?rrors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/playwright.base.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index b7e7b9a2e8..ce52657f85 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -1,7 +1,7 @@ import type { ReporterDescription, Config } from '@playwright/test' import { getTestReportDirectory } from '../envUtils' -const reporters: ReporterDescription[] = [['line'], ['./notice-reporter.ts']] +const reporters: ReporterDescription[] = [['line'], ['./noticeReporter.ts']] const testReportDirectory = getTestReportDirectory() if (testReportDirectory) { From 7b726a01d59d71fcf45bb3f2e1bd70188d003961 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:19:38 +0100 Subject: [PATCH 31/68] =?UTF-8?q?fixup!=20=F0=9F=91=8C=20fix=20linting=20e?= =?UTF-8?q?rrors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 3 ++- .../developer-extension/developerExtension.scenario.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 3bba4769f0..d243f77a46 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -4,7 +4,8 @@ import { DefaultPrivacyLevel } from '@datadog/browser-rum' import type { BrowserContext, Page } from '@playwright/test' import { test, expect } from '@playwright/test' import { getRunId } from '../../../envUtils' -import type { BrowserLog, BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' +import type { BrowserLog } from '../helpers/browser' +import { BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import { IntakeRegistry } from './intakeRegistry' diff --git a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts index eb48a4ea86..dcb4aa8fbf 100644 --- a/test/e2e/scenario/developer-extension/developerExtension.scenario.ts +++ b/test/e2e/scenario/developer-extension/developerExtension.scenario.ts @@ -7,7 +7,8 @@ const test = base.extend<{ extensionId: string developerExtension: DeveloperExtensionPage }>({ - context: async (_fixture, use) => { + // eslint-disable-next-line no-empty-pattern + context: async ({}, use) => { const pathToExtension = path.join(process.cwd(), 'developer-extension', 'dist') const context = await chromium.launchPersistentContext('', { From 436c127c12d37c1448004fc8c4efcd14325a0b33 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:48:51 +0100 Subject: [PATCH 32/68] =?UTF-8?q?=F0=9F=91=B7=20save=20html=20report=20to?= =?UTF-8?q?=20gitlab=20artifacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/playwright.base.config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index ce52657f85..7d7c25b61e 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -1,16 +1,16 @@ +import path from 'path' import type { ReporterDescription, Config } from '@playwright/test' import { getTestReportDirectory } from '../envUtils' +const testReportDirectory = getTestReportDirectory() + const reporters: ReporterDescription[] = [['line'], ['./noticeReporter.ts']] -const testReportDirectory = getTestReportDirectory() if (testReportDirectory) { - reporters.push([ - 'junit', - { - outputFile: `../../${testReportDirectory}/results.xml`, - }, - ]) + const outputFolder = path.join(process.cwd(), testReportDirectory) + + reporters.push(['html', { outputFolder }]) + reporters.push(['junit', { outputFile: path.join(outputFolder, 'results.xml') }]) } else { reporters.push(['html']) } @@ -24,7 +24,7 @@ export const config: Config = { fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - workers: 20, + workers: 25, reporter: reporters, use: { trace: 'on-first-retry', From e333f6d452912bd64925b950648de24989e7c003 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 09:50:47 +0100 Subject: [PATCH 33/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20type=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index d243f77a46..1f97925043 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -243,7 +243,7 @@ function createTestContext( }, flushEvents: () => flushEvents(page), deleteAllCookies: () => deleteAllCookies(browserContext), - sendXhr: (url: string, headers: string[][]) => sendXhr(page, url, headers), + sendXhr: (url: string, headers?: string[][]) => sendXhr(page, url, headers), } } From c63f4f79e0b5e0c73d1168cea4a78813ca924379 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 12:48:13 +0100 Subject: [PATCH 34/68] =?UTF-8?q?=F0=9F=91=B7=20setup=20browserstack=20e2e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- scripts/test/bs-wrapper.js | 42 ++++++++++++++++++++ test/browsers.conf.d.ts | 4 +- test/e2e/browsers.conf.js | 36 ++++++++--------- test/e2e/playwright.base.config.ts | 2 +- test/e2e/playwright.bs.config.ts | 47 +++++++++++++++++++++++ test/e2e/scenario/rum/actions.scenario.ts | 2 +- test/e2e/scenario/rum/errors.scenario.ts | 2 +- test/e2e/scenario/rum/views.scenario.ts | 2 +- test/e2e/scenario/transport.scenario.ts | 2 +- test/unit/browsers.conf.js | 2 +- 11 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 test/e2e/playwright.bs.config.ts diff --git a/package.json b/package.json index 372a8d2894..38b51a33cd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "test:unit:watch": "yarn test:unit --no-single-run", "test:unit:bs": "node ./scripts/test/bs-wrapper.js karma start test/unit/karma.bs.conf.js", "test:e2e": "playwright test --config test/e2e/playwright.local.config.ts", - "test:e2e:bs": "yarn build && (cd test/app && rm -rf node_modules && yarn && yarn build) && node ./scripts/test/bs-wrapper.js wdio test/e2e/wdio.bs.conf.ts", + "test:e2e:bs": "node ./scripts/test/bs-wrapper.js playwright test --config test/e2e/playwright.bs.config.ts", "test:e2e:developer-extension": "yarn build && wdio test/e2e/wdio.developer-extension.conf.ts", "test:compat:tsc": "scripts/cli check_typescript_compatibility", "test:compat:ssr": "scripts/cli check_server_side_rendering_compatibility", diff --git a/scripts/test/bs-wrapper.js b/scripts/test/bs-wrapper.js index 4252af3ac7..cc73c42fd5 100644 --- a/scripts/test/bs-wrapper.js +++ b/scripts/test/bs-wrapper.js @@ -15,6 +15,7 @@ // to in the future. const spawn = require('child_process').spawn +const browserStack = require('browserstack-local') const { printLog, runMain, timeout, printError } = require('../lib/executionUtils') const { command } = require('../lib/command') const { browserStackRequest } = require('../lib/bsUtils') @@ -28,8 +29,16 @@ runMain(async () => { printLog('Skip bs execution on tags') return } + + if (!process.env.BS_USERNAME || !process.env.BS_ACCESS_KEY) { + printError('Missing Browserstack credentials (BS_ACCESS_KEY and BS_USERNAME env variables)') + return + } + await waitForAvailability() + await startBsLocal() const isSuccess = await runTests() + await stopBsLocal() process.exit(isSuccess ? 0 : 1) }) @@ -44,6 +53,39 @@ async function hasRunningBuild() { return (await browserStackRequest(BS_BUILD_URL)).length > 0 } +const bsLocal = new browserStack.Local() + +function startBsLocal() { + printLog('Starting BrowserStackLocal...') + + return new Promise((resolve) => { + bsLocal.start( + { + key: process.env.BS_ACCESS_KEY, + forceLocal: true, + forceKill: true, + onlyAutomate: true, + }, + (error) => { + if (error) { + printError('Failed to start BrowserStackLocal:', error) + process.exit(1) + } + printLog('BrowserStackLocal started', bsLocal.isRunning()) + resolve() + } + ) + }) +} + +function stopBsLocal() { + return new Promise((resolve) => { + bsLocal.stop(() => { + resolve() + }) + }) +} + function runTests() { return new Promise((resolve) => { const [command, ...args] = process.argv.slice(2) diff --git a/test/browsers.conf.d.ts b/test/browsers.conf.d.ts index b6cb5a1ed6..572b50de9f 100644 --- a/test/browsers.conf.d.ts +++ b/test/browsers.conf.d.ts @@ -1,8 +1,8 @@ -export type BrowserConfigurations = Array<{ +export type BrowserConfiguration = { sessionName: string name: string version?: string os: string osVersion: string device?: string -}> +} diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index 65a3b1e889..3bd96b8e11 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -1,37 +1,37 @@ // Capabilities: https://www.browserstack.com/automate/capabilities /** - * @type {import('../browsers.conf').BrowserConfigurations} + * @type {Array} */ const browserConfigurations = [ { sessionName: 'Edge', - name: 'Edge', + name: 'edge', version: '100.0', os: 'Windows', osVersion: '11', }, { sessionName: 'Firefox', - name: 'Firefox', - version: '91.0', + name: 'playwright-firefox', + version: '132', os: 'Windows', osVersion: '11', }, - { - sessionName: 'Safari desktop', - name: 'Safari', - version: '14.1', - os: 'OS X', - osVersion: 'Big Sur', - }, - { - sessionName: 'Chrome mobile', - name: 'chrome', - os: 'android', - osVersion: '12.0', - device: 'Google Pixel 6 Pro', - }, + // { + // sessionName: 'Safari desktop', + // name: 'playwright-webkit', + // version: '15.4', + // os: 'OS X', + // osVersion: 'Big Sur', + // }, + // { + // sessionName: 'Chrome mobile', + // name: 'chrome', + // os: 'android', + // osVersion: '12.0', + // device: 'Google Pixel 6 Pro', + // }, ] module.exports = { diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 7d7c25b61e..195e83a043 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -23,7 +23,7 @@ export const config: Config = { testMatch: ['**/*.scenario.ts'], fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 1 : 0, workers: 25, reporter: reporters, use: { diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts new file mode 100644 index 0000000000..4b1500fbd6 --- /dev/null +++ b/test/e2e/playwright.bs.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from '@playwright/test' +import type { BrowserConfiguration } from '../browsers.conf' +import { getBuildInfos } from '../envUtils' +import { config as baseConfig } from './playwright.base.config' +import { browserConfigurations } from './browsers.conf' + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + ...baseConfig, + workers: 5, + // The following test won't run in the BrowserStack + testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], + projects: browserConfigurations.map((configuration) => ({ + name: configuration.name, + use: { + connectOptions: { + wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, + }, + }, + })), +}) + +function getEncodedCapabilities(configuration: BrowserConfiguration) { + return encodeURIComponent(JSON.stringify(getCapabilities(configuration))) +} + +function getCapabilities(configuration: BrowserConfiguration) { + return { + os: configuration.os, + os_version: configuration.osVersion, + browser: configuration.name, + browser_version: configuration.version, + device: configuration.device, + 'browserstack.username': process.env.BS_USERNAME, + 'browserstack.accessKey': process.env.BS_ACCESS_KEY, + project: 'browser sdk e2e', + build: getBuildInfos(), + name: configuration.sessionName, + 'browserstack.local': 'true', + 'browserstack.playwrightVersion': '1.49.0', // TODO fixme + 'client.playwrightVersion': '1.49.0', + 'browserstack.debug': 'true', // enabling visual logs + 'browserstack.console': 'info', // Enabling Console logs for the test + 'browserstack.networkLogs': 'true', // Enabling network logs for the test + 'browserstack.interactiveDebugging': 'false', + } +} diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 1f28e3f31a..172114ee7c 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -99,7 +99,7 @@ test.describe('action collection', () => { `) .run(async ({ intakeRegistry, flushEvents, browserName, page }) => { test.skip( - browserName === 'firefox', + browserName.includes('firefox'), 'When the target element changes between mousedown and mouseup, Firefox does not dispatch a click event.' ) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 87cf12b27e..3f26112cbd 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -137,7 +137,7 @@ test.describe('rum errors', () => { ) .run(async ({ page, browserName, intakeRegistry, baseUrl, flushEvents, withBrowserLogs }) => { const userAgent = await page.evaluate(() => navigator.userAgent) - test.skip(browserName === 'firefox' || (browserName === 'webkit' && userAgent.includes('Mac OS X'))) + test.skip(browserName.includes('firefox') || (browserName.includes('webkit') && userAgent.includes('Mac OS X'))) const button = page.locator('button') await button.click() diff --git a/test/e2e/scenario/rum/views.scenario.ts b/test/e2e/scenario/rum/views.scenario.ts index 19b82ebed9..cb9fe63a05 100644 --- a/test/e2e/scenario/rum/views.scenario.ts +++ b/test/e2e/scenario/rum/views.scenario.ts @@ -20,7 +20,7 @@ test.describe('rum views', () => { .withBody(html` `) .run(async ({ browserName, flushEvents, intakeRegistry, page }) => { test.skip( - browserName === 'webkit', + browserName.includes('webkit'), ` // When run via WebDriver, Safari <= 14 (at least) have an issue with 'event.timeStamp', // so the 'first-input' polyfill is ignoring it and doesn't send a performance entry. diff --git a/test/e2e/scenario/transport.scenario.ts b/test/e2e/scenario/transport.scenario.ts index 72bc54d78e..2bc788f2c1 100644 --- a/test/e2e/scenario/transport.scenario.ts +++ b/test/e2e/scenario/transport.scenario.ts @@ -36,7 +36,7 @@ test.describe('transport', () => { .run(async ({ browserName, flushEvents, intakeRegistry, page, withBrowserLogs }) => { const userAgent = await page.evaluate(() => navigator.userAgent) test.skip( - browserName === 'firefox' || (browserName === 'webkit' && userAgent.includes('Mac OS X')), + browserName.includes('firefox') || (browserName.includes('webkit') && userAgent.includes('Mac OS X')), ` // Ignore this test on Safari desktop and Firefox because the Worker actually works even with // CSP restriction. diff --git a/test/unit/browsers.conf.js b/test/unit/browsers.conf.js index 83ce85398b..39122ee098 100644 --- a/test/unit/browsers.conf.js +++ b/test/unit/browsers.conf.js @@ -1,7 +1,7 @@ // Capabilities: https://www.browserstack.com/automate/capabilities /** - * @type {import('../browsers.conf').BrowserConfigurations} + * @type {Array} */ const browserConfigurations = [ { From 0ea6b62d5599b69583f571dc5e6ee682ad7c6949 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 13:17:34 +0100 Subject: [PATCH 35/68] =?UTF-8?q?=F0=9F=91=B7=20makes=20bs=20start=20earli?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2ddb134128..edac16c06f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -220,7 +220,7 @@ e2e: artifacts: when: always paths: - - 'test-report/e2e/*' + - 'test-report/e2e/index.html' reports: junit: test-report/e2e/*.xml script: @@ -262,6 +262,7 @@ check-schemas: unit-bs: stage: browserstack + needs: ['unit'] extends: - .base-configuration - .bs-allowed-branches @@ -278,6 +279,7 @@ unit-bs: e2e-bs: stage: browserstack + needs: ['e2e'] extends: - .base-configuration - .bs-allowed-branches @@ -287,7 +289,8 @@ e2e-bs: timeout: 35 minutes artifacts: when: always - paths: ['test-report/e2e-bs/specs.log'] + paths: + - 'test-report/e2e/index.html' reports: junit: test-report/e2e-bs/*.xml script: From fb4988f8008c956ff1716e6c46940f9c05c2334d Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 14:48:02 +0100 Subject: [PATCH 36/68] =?UTF-8?q?=F0=9F=91=8C=20add=20build=20steps=20to?= =?UTF-8?q?=20e2e-bs=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index edac16c06f..a1e0799a1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -295,6 +295,8 @@ e2e-bs: junit: test-report/e2e-bs/*.xml script: - yarn + - yarn build + - yarn build:app - FORCE_COLOR=1 ./scripts/test/ci-bs.sh test:e2e after_script: - node ./scripts/test/export-test-result.js e2e-bs From c7dfc498d4be089417163c36b236b5aa3250af99 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 15:42:14 +0100 Subject: [PATCH 37/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20unit-bs=20job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/test/getCurrentJasmineSpec.ts | 21 +++++++------------ packages/core/test/registerCleanupTask.ts | 11 +++------- packages/rum/test/mutationPayloadValidator.ts | 3 ++- .../scenario/recorder/shadowDom.scenario.ts | 14 +++++++------ 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/packages/core/test/getCurrentJasmineSpec.ts b/packages/core/test/getCurrentJasmineSpec.ts index eb2de3fa99..59a114abe3 100644 --- a/packages/core/test/getCurrentJasmineSpec.ts +++ b/packages/core/test/getCurrentJasmineSpec.ts @@ -1,19 +1,14 @@ let currentSpec: jasmine.SpecResult | null = null export function getCurrentJasmineSpec() { - if (typeof globalThis['jasmine'] !== 'object') { - throw new Error('Not inside a Jasmine test') - } return currentSpec } -if (typeof globalThis['jasmine'] === 'object') { - globalThis['jasmine'].getEnv().addReporter({ - specStarted(specResult) { - currentSpec = specResult - }, - specDone() { - currentSpec = null - }, - }) -} +jasmine.getEnv().addReporter({ + specStarted(specResult) { + currentSpec = specResult + }, + specDone() { + currentSpec = null + }, +}) diff --git a/packages/core/test/registerCleanupTask.ts b/packages/core/test/registerCleanupTask.ts index af9edf9360..fcc3ad459a 100644 --- a/packages/core/test/registerCleanupTask.ts +++ b/packages/core/test/registerCleanupTask.ts @@ -1,14 +1,9 @@ const cleanupTasks: Array<() => void> = [] export function registerCleanupTask(task: () => void) { - if (typeof globalThis['afterEach'] !== 'function') { - throw new Error('Not inside a Jasmine test') - } cleanupTasks.unshift(task) } -if (typeof globalThis['afterEach'] === 'function') { - afterEach(() => { - cleanupTasks.splice(0).forEach((task) => task()) - }) -} +afterEach(() => { + cleanupTasks.splice(0).forEach((task) => task()) +}) diff --git a/packages/rum/test/mutationPayloadValidator.ts b/packages/rum/test/mutationPayloadValidator.ts index ef528c04f1..fee146719f 100644 --- a/packages/rum/test/mutationPayloadValidator.ts +++ b/packages/rum/test/mutationPayloadValidator.ts @@ -1,3 +1,4 @@ +import { getGlobalObject } from '@datadog/browser-core' import { NodeType, IncrementalSource } from '../src/types' import type { SerializedNodeWithId, @@ -127,7 +128,7 @@ export function createMutationPayloadValidator(initialDocument: SerializedNodeWi validate: ( payload: BrowserMutationPayload, expected: ExpectedMutationsPayload, - { expect = globalThis.expect }: { expect?: Expect } = {} + { expect = getGlobalObject().expect }: { expect?: Expect } = {} ) => { payload = removeUndefinedValues(payload) diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index 9ed4fd87ad..596065a308 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -6,17 +6,19 @@ import type { } from '@datadog/browser-rum/src/types' import { IncrementalSource, MouseInteractionType, NodeType } from '@datadog/browser-rum/src/types' +import { createMutationPayloadValidatorFromSegment } from '@datadog/browser-rum/test/mutationPayloadValidator' import { - createMutationPayloadValidatorFromSegment, findElementWithIdAttribute, findElementWithTagName, - findFullSnapshot, - findIncrementalSnapshot, - findMouseInteractionRecords, findNode, findTextContent, findTextNode, -} from '@datadog/browser-rum/test' +} from '@datadog/browser-rum/test/nodes' +import { + findFullSnapshot, + findIncrementalSnapshot, + findMouseInteractionRecords, +} from '@datadog/browser-rum/test/segments' import { test, expect } from '@playwright/test' import { createTest, bundleSetup, html } from '../../lib/framework' @@ -335,7 +337,7 @@ function findElementsInShadowDom(node: SerializedNodeWithId, id: string) { const text = findElementWithIdAttribute(node, `label-${id}`) expect(text).toBeTruthy() - const textContent = findTextContent(text!) + const textContent = findTextContent(text) expect(textContent).toBeTruthy() return { shadowHost, shadowRoot, input, text, textContent } } From f0668bfa7cf7805141c81aa1dec5a59b2d1fe0e7 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 15:52:24 +0100 Subject: [PATCH 38/68] =?UTF-8?q?=F0=9F=91=8C=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- scripts/test/bs-wrapper.js | 4 ++-- test/e2e/playwright.bs.config.ts | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a1e0799a1e..dbdbbf2b7b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -290,7 +290,7 @@ e2e-bs: artifacts: when: always paths: - - 'test-report/e2e/index.html' + - 'test-report/e2e/*' reports: junit: test-report/e2e-bs/*.xml script: diff --git a/scripts/test/bs-wrapper.js b/scripts/test/bs-wrapper.js index cc73c42fd5..36f9d5c019 100644 --- a/scripts/test/bs-wrapper.js +++ b/scripts/test/bs-wrapper.js @@ -24,6 +24,8 @@ const AVAILABILITY_CHECK_DELAY = 30_000 const NO_OUTPUT_TIMEOUT = 5 * 60_000 const BS_BUILD_URL = 'https://api.browserstack.com/automate/builds.json?status=running' +const bsLocal = new browserStack.Local() + runMain(async () => { if (command`git tag --points-at HEAD`.run()) { printLog('Skip bs execution on tags') @@ -53,8 +55,6 @@ async function hasRunningBuild() { return (await browserStackRequest(BS_BUILD_URL)).length > 0 } -const bsLocal = new browserStack.Local() - function startBsLocal() { printLog('Starting BrowserStackLocal...') diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index 4b1500fbd6..5f7fe6d61f 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -7,9 +7,8 @@ import { browserConfigurations } from './browsers.conf' // eslint-disable-next-line import/no-default-export export default defineConfig({ ...baseConfig, - workers: 5, - // The following test won't run in the BrowserStack - testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], + workers: 5, // BrowserStack has a limit of 5 parallel sessions + testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack projects: browserConfigurations.map((configuration) => ({ name: configuration.name, use: { @@ -24,6 +23,7 @@ function getEncodedCapabilities(configuration: BrowserConfiguration) { return encodeURIComponent(JSON.stringify(getCapabilities(configuration))) } +// see: https://www.browserstack.com/docs/automate/playwright/playwright-capabilities function getCapabilities(configuration: BrowserConfiguration) { return { os: configuration.os, @@ -37,11 +37,11 @@ function getCapabilities(configuration: BrowserConfiguration) { build: getBuildInfos(), name: configuration.sessionName, 'browserstack.local': 'true', - 'browserstack.playwrightVersion': '1.49.0', // TODO fixme - 'client.playwrightVersion': '1.49.0', - 'browserstack.debug': 'true', // enabling visual logs - 'browserstack.console': 'info', // Enabling Console logs for the test - 'browserstack.networkLogs': 'true', // Enabling network logs for the test + 'browserstack.playwrightVersion': '1.latest', + 'client.playwrightVersion': '1.latest', + 'browserstack.debug': 'false', + 'browserstack.console': 'info', + 'browserstack.networkLogs': 'false', 'browserstack.interactiveDebugging': 'false', } } From 92b6fdb6442605068ba7a8eadb5420b32b56cb53 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 15:56:50 +0100 Subject: [PATCH 39/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20typecheck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/recorder/shadowDom.scenario.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index 596065a308..aca68f6033 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -337,7 +337,7 @@ function findElementsInShadowDom(node: SerializedNodeWithId, id: string) { const text = findElementWithIdAttribute(node, `label-${id}`) expect(text).toBeTruthy() - const textContent = findTextContent(text) + const textContent = findTextContent(text!) expect(textContent).toBeTruthy() return { shadowHost, shadowRoot, input, text, textContent } } From 5fe7fe2adf86f7b107b69f2f8e72ed3890705b25 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 28 Jan 2025 17:56:26 +0100 Subject: [PATCH 40/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20report=20path=20for?= =?UTF-8?q?=20gitlab=20artifacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dbdbbf2b7b..441656651f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -290,7 +290,7 @@ e2e-bs: artifacts: when: always paths: - - 'test-report/e2e/*' + - 'test-report/e2e-bs/*' reports: junit: test-report/e2e-bs/*.xml script: From 31f69175381c9edee0861188e43135729b788a1c Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 29 Jan 2025 12:34:32 +0100 Subject: [PATCH 41/68] =?UTF-8?q?=F0=9F=91=B7=20tweak=20playwright=20confi?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 4 ---- scripts/test/bs-wrapper.js | 1 + test/e2e/playwright.base.config.ts | 9 +++------ test/e2e/playwright.bs.config.ts | 1 + test/e2e/playwright.local.config.ts | 31 +++++++++++++++++++++++------ 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 441656651f..06427fdb7c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -219,8 +219,6 @@ e2e: interruptible: true artifacts: when: always - paths: - - 'test-report/e2e/index.html' reports: junit: test-report/e2e/*.xml script: @@ -289,8 +287,6 @@ e2e-bs: timeout: 35 minutes artifacts: when: always - paths: - - 'test-report/e2e-bs/*' reports: junit: test-report/e2e-bs/*.xml script: diff --git a/scripts/test/bs-wrapper.js b/scripts/test/bs-wrapper.js index 36f9d5c019..fb5e6c5fdf 100644 --- a/scripts/test/bs-wrapper.js +++ b/scripts/test/bs-wrapper.js @@ -81,6 +81,7 @@ function startBsLocal() { function stopBsLocal() { return new Promise((resolve) => { bsLocal.stop(() => { + printLog('BrowserStackLocal stopped') resolve() }) }) diff --git a/test/e2e/playwright.base.config.ts b/test/e2e/playwright.base.config.ts index 195e83a043..f3063f3d12 100644 --- a/test/e2e/playwright.base.config.ts +++ b/test/e2e/playwright.base.config.ts @@ -7,10 +7,7 @@ const testReportDirectory = getTestReportDirectory() const reporters: ReporterDescription[] = [['line'], ['./noticeReporter.ts']] if (testReportDirectory) { - const outputFolder = path.join(process.cwd(), testReportDirectory) - - reporters.push(['html', { outputFolder }]) - reporters.push(['junit', { outputFile: path.join(outputFolder, 'results.xml') }]) + reporters.push(['junit', { outputFile: path.join(process.cwd(), testReportDirectory, 'results.xml') }]) } else { reporters.push(['html']) } @@ -23,10 +20,10 @@ export const config: Config = { testMatch: ['**/*.scenario.ts'], fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 1 : 0, + retries: process.env.CI ? 2 : 0, workers: 25, reporter: reporters, use: { - trace: 'on-first-retry', + trace: process.env.CI ? 'off' : 'retain-on-failure', }, } diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index 5f7fe6d61f..f05d5bb9bd 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ ...baseConfig, workers: 5, // BrowserStack has a limit of 5 parallel sessions testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack + maxFailures: process.env.CI ? 1 : 0, projects: browserConfigurations.map((configuration) => ({ name: configuration.name, use: { diff --git a/test/e2e/playwright.local.config.ts b/test/e2e/playwright.local.config.ts index ee1d1a45bc..8bdf3a2b3c 100644 --- a/test/e2e/playwright.local.config.ts +++ b/test/e2e/playwright.local.config.ts @@ -1,13 +1,32 @@ import { defineConfig, devices } from '@playwright/test' import { config as baseConfig } from './playwright.base.config' +const projects = [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, +] + +if (!process.env.CI) { + projects.push( + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'android', + use: { ...devices['Pixel 7'] }, + } + ) +} + // eslint-disable-next-line import/no-default-export export default defineConfig({ ...baseConfig, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], + projects, }) From e259cdda071e80451ed21558b12468d50533b8c4 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 29 Jan 2025 14:04:26 +0100 Subject: [PATCH 42/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20tests=20for=20firefo?= =?UTF-8?q?x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 8 +++++++- test/e2e/lib/framework/serverApps/mock.ts | 1 + test/e2e/lib/helpers/browser.ts | 2 +- test/e2e/scenario/logs.scenario.ts | 15 ++++++++++++--- test/e2e/scenario/rum/errors.scenario.ts | 4 +++- test/e2e/scenario/rum/sessions.scenario.ts | 8 +++++++- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 1f97925043..6b5fe34e4f 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -272,6 +272,13 @@ async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, deleteAllCookies }: TestContext) { await flushEvents() + await deleteAllCookies() + + if (test.info().expectedStatus === 'skipped') { + // ignore following expectations if test was skipped + return + } + expect(intakeRegistry.telemetryErrorEvents).toEqual([]) validateRumFormat(intakeRegistry.rumEvents) withBrowserLogs((logs) => { @@ -280,5 +287,4 @@ async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, dele }) expect(logs.filter((log) => log.level === 'error')).toEqual([]) }) - await deleteAllCookies() } diff --git a/test/e2e/lib/framework/serverApps/mock.ts b/test/e2e/lib/framework/serverApps/mock.ts index 6e748345c4..a18f9c824a 100644 --- a/test/e2e/lib/framework/serverApps/mock.ts +++ b/test/e2e/lib/framework/serverApps/mock.ts @@ -74,6 +74,7 @@ export function createMockServerApp(servers: Servers, setup: string): MockServer }) app.get('/ok', (req, res) => { + res.header('Content-Type', 'text/plain') if (req.query['timing-allow-origin'] === 'true') { res.set('Timing-Allow-Origin', '*') } diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 33bec3fec7..7cd4775cef 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -74,7 +74,7 @@ export class BrowserLogsManager { } get() { - return this.logs + return this.logs.filter((log) => !log.message.includes('Ignoring unsupported entryTypes: longtask')) } clear() { diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index bb6662fd1b..ff57cc5387 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -94,10 +94,19 @@ test.describe('logs', () => { }) }) - createTest('keep only the first bytes of the response') + createTest('keep only the first bytes of the response @fixme') .withLogs({ forwardErrorsToLogs: true }) - .run(async ({ intakeRegistry, baseUrl, servers, flushEvents, page, withBrowserLogs }) => { - await page.evaluate(() => fetch('/throw-large-response').then(() => undefined, console.log)) + .run(async ({ intakeRegistry, baseUrl, servers, flushEvents, page, withBrowserLogs, browserName }) => { + await page.evaluate( + () => + new Promise((resolve) => { + fetch('/throw-large-response') + .catch(console.log) + .finally(() => resolve()) + }) + ) + + test.fixme(browserName.includes('firefox'), 'This does not pass in FF') await flushEvents() expect(intakeRegistry.logsEvents.length).toBe(1) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 3f26112cbd..60c01dc161 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -190,7 +190,9 @@ function expectStack(stack: string | undefined, expectedLines?: Array { if (typeof line !== 'string') { return expect(actualLines[i]).toMatch(line) diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 0b30bce66a..360de118e1 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -114,12 +114,18 @@ test.describe('rum sessions', () => { expect(intakeRegistry.rumActionEvents.length).toBe(0) }) - createTest('after calling stopSession(), a user interaction starts a new session') + createTest('after calling stopSession(), a user interaction starts a new session @known-flacky') .withRum() .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { await page.evaluate(() => { window.DD_RUM!.stopSession() }) + + test.info().annotations.push({ + type: 'known-flacky', + description: 'This test is known to be flacky, especially in FF', + }) + await page.locator('html').click() // The session is not created right away, let's wait until we see a cookie From 928f241b9a81a70b710bffd20de582d86f9424ae Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 29 Jan 2025 14:09:23 +0100 Subject: [PATCH 43/68] =?UTF-8?q?=F0=9F=91=B7=20use=20.toHaveLength()=20as?= =?UTF-8?q?sertion=20when=20possible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/eventBridge.scenario.ts | 8 +-- test/e2e/scenario/logs.scenario.ts | 20 ++++---- test/e2e/scenario/microfrontend.scenario.ts | 2 +- .../scenario/recorder/recorder.scenario.ts | 50 +++++++++---------- .../scenario/recorder/shadowDom.scenario.ts | 12 ++--- test/e2e/scenario/rum/actions.scenario.ts | 36 ++++++------- test/e2e/scenario/rum/errors.scenario.ts | 12 ++--- test/e2e/scenario/rum/init.scenario.ts | 6 +-- test/e2e/scenario/rum/resources.scenario.ts | 2 +- test/e2e/scenario/rum/sessions.scenario.ts | 22 ++++---- test/e2e/scenario/rum/tracing.scenario.ts | 2 +- test/e2e/scenario/rum/views.scenario.ts | 4 +- test/e2e/scenario/rum/vitals.scenario.ts | 2 +- test/e2e/scenario/telemetry.scenario.ts | 12 ++--- test/e2e/scenario/transport.scenario.ts | 4 +- 15 files changed, 97 insertions(+), 97 deletions(-) diff --git a/test/e2e/scenario/eventBridge.scenario.ts b/test/e2e/scenario/eventBridge.scenario.ts index a9d934f809..ff406d526a 100644 --- a/test/e2e/scenario/eventBridge.scenario.ts +++ b/test/e2e/scenario/eventBridge.scenario.ts @@ -21,7 +21,7 @@ test.describe('bridge present', () => { await page.waitForTimeout(1000) await flushEvents() - expect(intakeRegistry.rumActionEvents.length).toBe(1) + expect(intakeRegistry.rumActionEvents).toHaveLength(1) expect(intakeRegistry.hasOnlyBridgeRequests).toBe(true) }) @@ -36,7 +36,7 @@ test.describe('bridge present', () => { flushBrowserLogs() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expect(intakeRegistry.hasOnlyBridgeRequests).toBe(true) }) @@ -74,7 +74,7 @@ test.describe('bridge present', () => { }) await flushEvents() - expect(intakeRegistry.telemetryErrorEvents.length).toBe(1) + expect(intakeRegistry.telemetryErrorEvents).toHaveLength(1) expect(intakeRegistry.hasOnlyBridgeRequests).toBe(true) intakeRegistry.empty() }) @@ -88,7 +88,7 @@ test.describe('bridge present', () => { }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.hasOnlyBridgeRequests).toBe(true) }) diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index ff57cc5387..db88762a0e 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -13,7 +13,7 @@ test.describe('logs', () => { window.DD_LOGS!.logger.log('hello') }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe('hello') }) @@ -25,10 +25,10 @@ test.describe('logs', () => { window.DD_LOGS!.logger.warn('hello') }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(0) + expect(intakeRegistry.logsEvents).toHaveLength(0) withBrowserLogs((logs) => { - expect(logs.length).toBe(1) + expect(logs).toHaveLength(1) expect(logs[0].level).toBe('warning') expect(logs[0].message).not.toEqual(expect.stringContaining('Datadog Browser SDK')) expect(logs[0].message).toEqual(expect.stringContaining('hello')) @@ -42,7 +42,7 @@ test.describe('logs', () => { console.error('oh snap') }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe('oh snap') withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) @@ -64,7 +64,7 @@ test.describe('logs', () => { ) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe(`XHR error GET ${UNREACHABLE_URL}`) expect(intakeRegistry.logsEvents[0].origin).toBe('network') @@ -82,7 +82,7 @@ test.describe('logs', () => { await page.evaluate((unreachableUrl) => fetch(unreachableUrl).catch(() => undefined), UNREACHABLE_URL) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe(`Fetch error GET ${UNREACHABLE_URL}`) expect(intakeRegistry.logsEvents[0].origin).toBe('network') @@ -109,12 +109,12 @@ test.describe('logs', () => { test.fixme(browserName.includes('firefox'), 'This does not pass in FF') await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe(`Fetch error GET ${baseUrl}/throw-large-response`) expect(intakeRegistry.logsEvents[0].origin).toBe('network') const ellipsisSize = 3 - expect(intakeRegistry.logsEvents[0].error?.stack?.length).toBe( + expect(intakeRegistry.logsEvents[0].error?.stack).toHaveLength( DEFAULT_REQUEST_ERROR_RESPONSE_LENGTH_LIMIT + ellipsisSize ) @@ -183,7 +183,7 @@ test.describe('logs', () => { window.DD_LOGS!.logger.log('hello') }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].view.id).toBeDefined() expect(intakeRegistry.logsEvents[0].application_id).toBe(APPLICATION_ID) }) @@ -200,7 +200,7 @@ test.describe('logs', () => { window.DD_LOGS!.logger.log('hello', {}) }) await flushEvents() - expect(intakeRegistry.logsEvents.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].foo).toBe('bar') }) }) diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts index 0e6cd72a91..2df86818c6 100644 --- a/test/e2e/scenario/microfrontend.scenario.ts +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -130,7 +130,7 @@ test.describe('microfrontend', () => { const event = intakeRegistry.rumErrorEvents[0] withBrowserLogs((logs) => { - expect(logs.length).toBe(1) + expect(logs).toHaveLength(1) expect(logs[0].message).toMatch(/foo$/) // TODO(playwright migration): it looks like chrome is logging the string without double quotes, but some other browser might }) diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 5df6ed14d7..36d6ec2f8d 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -34,7 +34,7 @@ test.describe('recorder', () => { await page.locator('html').click() await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const { segment, metadata, @@ -93,7 +93,7 @@ test.describe('recorder', () => { .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! @@ -104,13 +104,13 @@ test.describe('recorder', () => { const hiddenNodeByAttribute = findElement(fullSnapshot.data.node, (node) => node.tagName === 'p') expect(hiddenNodeByAttribute).toBeTruthy() expect(hiddenNodeByAttribute!.attributes['data-dd-privacy']).toBe('hidden') - expect(hiddenNodeByAttribute!.childNodes.length).toBe(0) + expect(hiddenNodeByAttribute!.childNodes).toHaveLength(0) const hiddenNodeByClassName = findElement(fullSnapshot.data.node, (node) => node.tagName === 'span') expect(hiddenNodeByClassName).toBeTruthy() expect(hiddenNodeByClassName!.attributes.class).toBeUndefined() expect(hiddenNodeByClassName!.attributes['data-dd-privacy']).toBe('hidden') - expect(hiddenNodeByClassName!.childNodes.length).toBe(0) + expect(hiddenNodeByClassName!.childNodes).toHaveLength(0) const inputIgnored = findElementWithIdAttribute(fullSnapshot.data.node, 'input-not-obfuscated') expect(inputIgnored).toBeTruthy() @@ -283,7 +283,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] expect(findAllIncrementalSnapshots(segment, IncrementalSource.Mutation)).toEqual([]) @@ -496,12 +496,12 @@ test.describe('recorder', () => { expect((textInputRecords[textInputRecords.length - 1].data as { text?: string }).text).toBe('test') const radioInputRecords = filterRecordsByIdAttribute('radio-input') - expect(radioInputRecords.length).toBe(1) + expect(radioInputRecords).toHaveLength(1) expect((radioInputRecords[0].data as { text?: string }).text).toBe(undefined) expect((radioInputRecords[0].data as { isChecked?: boolean }).isChecked).toBe(true) const checkboxInputRecords = filterRecordsByIdAttribute('checkbox-input') - expect(checkboxInputRecords.length).toBe(1) + expect(checkboxInputRecords).toHaveLength(1) expect((checkboxInputRecords[0].data as { text?: string }).text).toBe(undefined) expect((checkboxInputRecords[0].data as { isChecked?: boolean }).isChecked).toBe(true) @@ -510,7 +510,7 @@ test.describe('recorder', () => { expect((textareaRecords[textareaRecords.length - 1].data as { text?: string }).text).toBe('textarea test') const selectRecords = filterRecordsByIdAttribute('select') - expect(selectRecords.length).toBe(1) + expect(selectRecords).toHaveLength(1) expect((selectRecords[0].data as { text?: string }).text).toBe('2') function filterRecordsByIdAttribute(idAttribute: string) { @@ -575,7 +575,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] @@ -609,7 +609,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] @@ -617,7 +617,7 @@ test.describe('recorder', () => { data: StyleSheetRuleData }> - expect(styleSheetRules.length).toBe(2) + expect(styleSheetRules).toHaveLength(2) expect(styleSheetRules[0].data.removes).toEqual([{ index: 0 }]) expect(styleSheetRules[1].data.adds).toEqual([{ rule: '.added {}', index: 0 }]) }) @@ -651,7 +651,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] @@ -659,7 +659,7 @@ test.describe('recorder', () => { data: StyleSheetRuleData }> - expect(styleSheetRules.length).toBe(3) + expect(styleSheetRules).toHaveLength(3) expect(styleSheetRules[0].data.adds).toEqual([{ rule: '.inserted {}', index: [0, 0] }]) expect(styleSheetRules[1].data.adds).toEqual([{ rule: '.added {}', index: [0, 1] }]) expect(styleSheetRules[2].data.removes).toEqual([{ index: [1, 1] }]) @@ -675,15 +675,15 @@ test.describe('recorder', () => { await html.click() await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] const mouseupRecords = findMouseInteractionRecords(segment, MouseInteractionType.MouseUp) const frustrationRecords = findAllFrustrationRecords(segment) - expect(mouseupRecords.length).toBe(1) + expect(mouseupRecords).toHaveLength(1) expect(mouseupRecords[0].id, 'mouse interaction record should have an id').toBeTruthy() - expect(frustrationRecords.length).toBe(1) + expect(frustrationRecords).toHaveLength(1) expect(frustrationRecords[0].data).toEqual({ frustrationTypes: [FrustrationType.DEAD_CLICK], recordIds: [mouseupRecords[0].id!], @@ -725,14 +725,14 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] const mouseupRecords = findMouseInteractionRecords(segment, MouseInteractionType.MouseUp) const frustrationRecords = findAllFrustrationRecords(segment) - expect(mouseupRecords.length).toBe(4) - expect(frustrationRecords.length).toBe(1) + expect(mouseupRecords).toHaveLength(4) + expect(frustrationRecords).toHaveLength(1) expect(frustrationRecords[0].data).toEqual({ frustrationTypes: [FrustrationType.RAGE_CLICK], recordIds: mouseupRecords.map((r) => r.id!), @@ -810,7 +810,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(2) + expect(intakeRegistry.replaySegments).toHaveLength(2) const firstSegment = intakeRegistry.replaySegments[0] const firstFullSnapshot = findFullSnapshot(firstSegment)! @@ -820,7 +820,7 @@ test.describe('recorder', () => { expect(containerElement.attributes.rr_scrollLeft).toBe(10) const scrollRecords = findAllIncrementalSnapshots(firstSegment, IncrementalSource.Scroll) - expect(scrollRecords.length).toBe(2) + expect(scrollRecords).toHaveLength(2) const [windowScrollData, containerScrollData] = scrollRecords.map((record) => record.data as ScrollData) expect(windowScrollData.y).toEqual(150) expect(containerScrollData.x).toEqual(20) @@ -844,7 +844,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(0) + expect(intakeRegistry.replaySegments).toHaveLength(0) }) createTest('should start recording if forced when session is sampled out') @@ -859,7 +859,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) }) }) @@ -874,7 +874,7 @@ test.describe('recorder', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(2) + expect(intakeRegistry.replaySegments).toHaveLength(2) const firstSegment = intakeRegistry.replaySegments[0] expect(findFullSnapshot(firstSegment), 'first segment have a FullSnapshot record').toBeTruthy() @@ -890,6 +890,6 @@ test.describe('recorder', () => { .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) }) }) diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index aca68f6033..311a4e6cdc 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -170,7 +170,7 @@ test.describe('recorder with shadow DOM', () => { .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! expect(fullSnapshot).toBeTruthy() @@ -193,7 +193,7 @@ test.describe('recorder with shadow DOM', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! expect(fullSnapshot).toBeTruthy() @@ -216,7 +216,7 @@ test.describe('recorder with shadow DOM', () => { .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! expect(fullSnapshot).toBeTruthy() @@ -251,7 +251,7 @@ test.describe('recorder with shadow DOM', () => { const div = page.locator('my-div div') await div.click() await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! const divNode = findElementWithTagName(fullSnapshot.data.node, 'div')! const mouseInteraction = findMouseInteractionRecords( @@ -276,7 +276,7 @@ test.describe('recorder with shadow DOM', () => { div.innerText = 'titi' }) await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const { validate, expectInitialNode, expectNewNode } = createMutationPayloadValidatorFromSegment( intakeRegistry.replaySegments[0], { expect } @@ -312,7 +312,7 @@ test.describe('recorder with shadow DOM', () => { await button.click() await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) const scrollRecord = findIncrementalSnapshot(intakeRegistry.replaySegments[0], IncrementalSource.Scroll) const fullSnapshot = findFullSnapshot(intakeRegistry.replaySegments[0])! const divNode = findElementWithIdAttribute(fullSnapshot.data.node, 'scrollable-div')! diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 172114ee7c..05d87436bf 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -19,7 +19,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0]).toEqual( expect.objectContaining({ action: { @@ -79,7 +79,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action?.target?.name).toBe('click me') expect(actionEvents[0]._dd.action?.target?.selector).toBe('BODY>BUTTON') }) @@ -108,7 +108,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action?.target?.name).toBe('click me') }) @@ -131,7 +131,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents const resourceEvents = intakeRegistry.rumResourceEvents.filter((event) => event.resource.type === 'fetch') - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action).toEqual({ error: { count: 0, @@ -153,9 +153,9 @@ test.describe('action collection', () => { }, }) - expect(resourceEvents.length).toBe(1) + expect(resourceEvents).toHaveLength(1) // resource action id should contain the collected action id + the discarded rage click id - expect(resourceEvents[0].action!.id.length).toBe(2) + expect(resourceEvents[0].action!.id).toHaveLength(2) expect(resourceEvents[0].action!.id).toContain(actionEvents[0].action.id!) }) @@ -175,7 +175,7 @@ test.describe('action collection', () => { await button.click() await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) const viewEvents = intakeRegistry.rumViewEvents const originalViewEvent = viewEvents.find((view) => view.view.url.endsWith('/'))! @@ -202,7 +202,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual(['error_click']) expect(actionEvents[0].action.error!.count).toBe(1) @@ -222,7 +222,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual(['dead_click']) expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(1) @@ -237,7 +237,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -250,7 +250,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -263,7 +263,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual(['dead_click']) }) @@ -276,7 +276,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -300,7 +300,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -326,7 +326,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(3) + expect(actionEvents).toHaveLength(3) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -348,7 +348,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual([]) }) @@ -384,7 +384,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toEqual(['rage_click']) }) @@ -405,7 +405,7 @@ test.describe('action collection', () => { await flushEvents() const actionEvents = intakeRegistry.rumActionEvents - expect(actionEvents.length).toBe(1) + expect(actionEvents).toHaveLength(1) expect(actionEvents[0].action.frustration!.type).toStrictEqual(['error_click', 'dead_click']) expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(2) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 60c01dc161..2a58b3e1a7 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -28,7 +28,7 @@ test.describe('rum errors', () => { await button.click() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: 'oh snap', source: 'console', @@ -48,7 +48,7 @@ test.describe('rum errors', () => { await button.click() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: 'Foo: Error: oh snap', source: 'console', @@ -69,7 +69,7 @@ test.describe('rum errors', () => { await button.click() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: 'oh snap', source: 'source', @@ -89,7 +89,7 @@ test.describe('rum errors', () => { await button.click() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: 'oh snap', source: 'source', @@ -109,7 +109,7 @@ test.describe('rum errors', () => { await button.click() await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: 'oh snap', source: 'custom', @@ -144,7 +144,7 @@ test.describe('rum errors', () => { await flushEvents() - expect(intakeRegistry.rumErrorEvents.length).toBe(1) + expect(intakeRegistry.rumErrorEvents).toHaveLength(1) expectError(intakeRegistry.rumErrorEvents[0].error, { message: /^csp_violation: 'https:\/\/example\.com\/foo\.js' blocked by 'script-src(-elem)?' directive$/, source: 'report', diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 9d7bf0130c..4210c305bd 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -11,7 +11,7 @@ test.describe('API calls and events around init', () => { }) .run(({ withBrowserLogs }) => { withBrowserLogs((logs) => { - expect(logs.length).toBe(1) + expect(logs).toHaveLength(1) expect(logs[0].message).toEqual(expect.stringContaining('Datadog Browser SDK')) expect(logs[0].message).toEqual(expect.stringContaining('Missing configuration')) }) @@ -232,7 +232,7 @@ function expectToHaveErrors( events: IntakeRegistry, ...errors: Array<{ message: string; viewId: string; context?: Context }> ) { - expect(events.rumErrorEvents.length).toBe(errors.length) + expect(events.rumErrorEvents).toHaveLength(errors.length) for (let i = 0; i < errors.length; i++) { const registryError = events.rumErrorEvents[i] const expectedError = errors[i] @@ -248,7 +248,7 @@ function expectToHaveActions( events: IntakeRegistry, ...actions: Array<{ name: string; viewId: string; viewName?: string; context?: Context }> ) { - expect(events.rumActionEvents.length).toBe(actions.length) + expect(events.rumActionEvents).toHaveLength(actions.length) for (let i = 0; i < actions.length; i++) { const registryAction = events.rumActionEvents[i] const expectedAction = actions[i] diff --git a/test/e2e/scenario/rum/resources.scenario.ts b/test/e2e/scenario/rum/resources.scenario.ts index e139cd060c..ce51281ade 100644 --- a/test/e2e/scenario/rum/resources.scenario.ts +++ b/test/e2e/scenario/rum/resources.scenario.ts @@ -289,7 +289,7 @@ test.describe('rum resources', () => { const resourceEvents = intakeRegistry.rumResourceEvents.filter((event) => event.resource.type === 'xhr') expect(resourceEvents.length).toEqual(2) - expect(intakeRegistry.rumErrorEvents.length).toBe(0) + expect(intakeRegistry.rumErrorEvents).toHaveLength(0) expect(resourceEvents[0].resource.url).toContain('/ok?duration=100&call=1') expect(resourceEvents[0].resource.status_code).toEqual(200) expect(resourceEvents[1].resource.url).toContain('/ok?duration=100&call=2') diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 360de118e1..477d209ddf 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -28,7 +28,7 @@ test.describe('rum sessions', () => { await flushEvents() - expect(intakeRegistry.replaySegments.length).toBe(2) + expect(intakeRegistry.replaySegments).toHaveLength(2) const segment = intakeRegistry.replaySegments.at(-1)! expect(segment.creation_reason).toBe('init') @@ -111,7 +111,7 @@ test.describe('rum sessions', () => { await flushEvents() expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') - expect(intakeRegistry.rumActionEvents.length).toBe(0) + expect(intakeRegistry.rumActionEvents).toHaveLength(0) }) createTest('after calling stopSession(), a user interaction starts a new session @known-flacky') @@ -139,16 +139,16 @@ test.describe('rum sessions', () => { expect((await findSessionCookie(browserContext))?.isExpired).not.toEqual('1') expect((await findSessionCookie(browserContext))?.id).toBeDefined() - expect(intakeRegistry.rumActionEvents.length).toBe(1) + expect(intakeRegistry.rumActionEvents).toHaveLength(1) }) createTest('flush events when the session expires') .withRum() .withLogs() .run(async ({ intakeRegistry, page }) => { - expect(intakeRegistry.rumViewEvents.length).toBe(0) - expect(intakeRegistry.logsEvents.length).toBe(0) - expect(intakeRegistry.replaySegments.length).toBe(0) + expect(intakeRegistry.rumViewEvents).toHaveLength(0) + expect(intakeRegistry.logsEvents).toHaveLength(0) + expect(intakeRegistry.replaySegments).toHaveLength(0) await page.evaluate(() => { window.DD_LOGS!.logger.log('foo') @@ -157,10 +157,10 @@ test.describe('rum sessions', () => { await waitForRequests(page) - expect(intakeRegistry.rumViewEvents.length).toBe(1) + expect(intakeRegistry.rumViewEvents).toHaveLength(1) expect(intakeRegistry.rumViewEvents[0].session.is_active).toBe(false) - expect(intakeRegistry.logsEvents.length).toBe(1) - expect(intakeRegistry.replaySegments.length).toBe(1) + expect(intakeRegistry.logsEvents).toHaveLength(1) + expect(intakeRegistry.replaySegments).toHaveLength(1) }) }) @@ -184,8 +184,8 @@ test.describe('rum sessions', () => { await flushEvents() expect(await findSessionCookie(browserContext)).toBeUndefined() - expect(intakeRegistry.rumActionEvents.length).toBe(0) - expect(intakeRegistry.rumViewEvents.length).toBe(1) + expect(intakeRegistry.rumActionEvents).toHaveLength(0) + expect(intakeRegistry.rumViewEvents).toHaveLength(1) expect(intakeRegistry.rumViewEvents[0].session.is_active).toBe(false) }) }) diff --git a/test/e2e/scenario/rum/tracing.scenario.ts b/test/e2e/scenario/rum/tracing.scenario.ts index 3c0d4fa985..ee88ef9f05 100644 --- a/test/e2e/scenario/rum/tracing.scenario.ts +++ b/test/e2e/scenario/rum/tracing.scenario.ts @@ -105,7 +105,7 @@ test.describe('tracing', () => { const requests = intakeRegistry.rumResourceEvents.filter( (event) => event.resource.type === 'xhr' || event.resource.type === 'fetch' ) - expect(requests.length).toBe(1) + expect(requests).toHaveLength(1) expect(requests[0]._dd.trace_id).toMatch(/\d+/) expect(requests[0]._dd.span_id).toMatch(/\d+/) expect(requests[0].resource.id).toBeDefined() diff --git a/test/e2e/scenario/rum/views.scenario.ts b/test/e2e/scenario/rum/views.scenario.ts index cb9fe63a05..bd00503772 100644 --- a/test/e2e/scenario/rum/views.scenario.ts +++ b/test/e2e/scenario/rum/views.scenario.ts @@ -49,7 +49,7 @@ test.describe('rum views', () => { await flushEvents() const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBe(1) + expect(viewEvents).toHaveLength(1) expect(viewEvents[0].view.loading_type).toBe('initial_load') }) @@ -63,7 +63,7 @@ test.describe('rum views', () => { await flushEvents() const viewEvents = intakeRegistry.rumViewEvents - expect(viewEvents.length).toBe(2) + expect(viewEvents).toHaveLength(2) expect(viewEvents[0].view.loading_type).toBe('initial_load') expect(viewEvents[1].view.loading_type).toBe('route_change') }) diff --git a/test/e2e/scenario/rum/vitals.scenario.ts b/test/e2e/scenario/rum/vitals.scenario.ts index f9eb5923aa..86417157d3 100644 --- a/test/e2e/scenario/rum/vitals.scenario.ts +++ b/test/e2e/scenario/rum/vitals.scenario.ts @@ -16,7 +16,7 @@ test.describe('vital collection', () => { }) await flushEvents() - expect(intakeRegistry.rumVitalEvents.length).toBe(1) + expect(intakeRegistry.rumVitalEvents).toHaveLength(1) expect(intakeRegistry.rumVitalEvents[0].vital.name).toEqual('foo') expect(intakeRegistry.rumVitalEvents[0].vital.duration).toEqual(expect.any(Number)) }) diff --git a/test/e2e/scenario/telemetry.scenario.ts b/test/e2e/scenario/telemetry.scenario.ts index fd8a1f46a6..6a13a616a7 100644 --- a/test/e2e/scenario/telemetry.scenario.ts +++ b/test/e2e/scenario/telemetry.scenario.ts @@ -15,7 +15,7 @@ test.describe('telemetry', () => { window.DD_LOGS!.logger.log('hop', context as any) }) await flushEvents() - expect(intakeRegistry.telemetryErrorEvents.length).toBe(1) + expect(intakeRegistry.telemetryErrorEvents).toHaveLength(1) const event = intakeRegistry.telemetryErrorEvents[0] expect(event.service).toEqual('browser-logs-sdk') expect(event.telemetry.message).toBe('expected error') @@ -37,7 +37,7 @@ test.describe('telemetry', () => { window.DD_RUM!.addAction('hop', context as any) }) await flushEvents() - expect(intakeRegistry.telemetryErrorEvents.length).toBe(1) + expect(intakeRegistry.telemetryErrorEvents).toHaveLength(1) const event = intakeRegistry.telemetryErrorEvents[0] expect(event.service).toEqual('browser-rum-sdk') expect(event.telemetry.message).toBe('expected error') @@ -53,7 +53,7 @@ test.describe('telemetry', () => { }) .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() - expect(intakeRegistry.telemetryConfigurationEvents.length).toBe(1) + expect(intakeRegistry.telemetryConfigurationEvents).toHaveLength(1) const event = intakeRegistry.telemetryConfigurationEvents[0] expect(event.service).toEqual('browser-logs-sdk') expect(event.telemetry.configuration.forward_errors_to_logs).toEqual(true) @@ -66,7 +66,7 @@ test.describe('telemetry', () => { }) .run(async ({ intakeRegistry, flushEvents }) => { await flushEvents() - expect(intakeRegistry.telemetryConfigurationEvents.length).toBe(1) + expect(intakeRegistry.telemetryConfigurationEvents).toHaveLength(1) const event = intakeRegistry.telemetryConfigurationEvents[0] expect(event.service).toEqual('browser-rum-sdk') expect(event.telemetry.configuration.track_user_interactions).toEqual(true) @@ -81,7 +81,7 @@ test.describe('telemetry', () => { }) await flushEvents() - expect(intakeRegistry.telemetryUsageEvents.length).toBe(2) + expect(intakeRegistry.telemetryUsageEvents).toHaveLength(2) const event = intakeRegistry.telemetryUsageEvents[1] // first event is 'set-global-context' done in pageSetup.ts expect(event.service).toEqual('browser-rum-sdk') expect(event.telemetry.usage.feature).toEqual('add-action') @@ -96,7 +96,7 @@ test.describe('telemetry', () => { }) await flushEvents() - expect(intakeRegistry.telemetryUsageEvents.length).toBe(1) + expect(intakeRegistry.telemetryUsageEvents).toHaveLength(1) const event = intakeRegistry.telemetryUsageEvents[0] expect(event.service).toEqual('browser-logs-sdk') expect(event.telemetry.usage.feature).toEqual('set-tracking-consent') diff --git a/test/e2e/scenario/transport.scenario.ts b/test/e2e/scenario/transport.scenario.ts index 2bc788f2c1..99641de18e 100644 --- a/test/e2e/scenario/transport.scenario.ts +++ b/test/e2e/scenario/transport.scenario.ts @@ -10,7 +10,7 @@ test.describe('transport', () => { .run(async ({ flushEvents, intakeRegistry }) => { await flushEvents() - expect(intakeRegistry.rumRequests.length).toBe(2) + expect(intakeRegistry.rumRequests).toHaveLength(2) const plainRequest = intakeRegistry.rumRequests.find((request) => request.encoding === null) const deflateRequest = intakeRegistry.rumRequests.find((request) => request.encoding === 'deflate') @@ -47,7 +47,7 @@ test.describe('transport', () => { // Some non-deflate request can still be sent because on some browsers the Worker fails // asynchronously - expect(intakeRegistry.rumRequests.filter((request) => request.encoding === 'deflate').length).toBe(0) + expect(intakeRegistry.rumRequests.filter((request) => request.encoding === 'deflate')).toHaveLength(0) withBrowserLogs((logs) => { const failedToStartLog = logs.find((log) => log.message.includes('Datadog RUM failed to start')) From ef9c84028e83d456b607107f93ee27f681d984ef Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 29 Jan 2025 16:41:02 +0100 Subject: [PATCH 44/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20isBrowserStack=20var?= =?UTF-8?q?iable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/pageSetups.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index f3f34e536b..e08ab71da2 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -21,10 +21,7 @@ export interface SetupOptions { export type SetupFactory = (options: SetupOptions, servers: Servers) => string -const isBrowserStack = false -// 'services' in browser.options && -// browser.options.services && -// browser.options.services.some((service) => (Array.isArray(service) ? service[0] : service) === 'browserstack') +const isBrowserStack = process.env.BS_USERNAME && process.env.BS_ACCESS_KEY const isContinuousIntegration = Boolean(process.env.CI_JOB_ID) From 2d1f4363e5acf94377bb5dd7d56f664e0008d3ba Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 29 Jan 2025 16:46:58 +0100 Subject: [PATCH 45/68] =?UTF-8?q?=F0=9F=91=B7=20remove=20logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Playwright can show network request in traces if needed --- test/e2e/lib/framework/createTest.ts | 6 ------ test/e2e/lib/framework/httpServers.ts | 12 ------------ test/e2e/lib/framework/logger.ts | 16 ---------------- 3 files changed, 34 deletions(-) delete mode 100644 test/e2e/lib/framework/logger.ts diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 6b5fe34e4f..d11a3237b6 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -12,7 +12,6 @@ import { IntakeRegistry } from './intakeRegistry' import { flushEvents } from './flushEvents' import type { Servers } from './httpServers' import { getTestServers, waitForServersIdle } from './httpServers' -import { log } from './logger' import type { SetupFactory, SetupOptions } from './pageSetups' import { DEFAULT_SETUPS, npmSetup } from './pageSetups' import { createIntakeServerApp } from './serverApps/intake' @@ -190,7 +189,6 @@ function declareTestsForSetups( function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { test(title, async ({ page, context, browserName }) => { const title = test.info().titlePath.join(' > ') - log(`Start '${title}' in ${browserName}`) setupOptions.context.test_name = title @@ -210,7 +208,6 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa await runner(testContext) } finally { await tearDownTest(testContext) - log(`End '${title}'`) } }) } @@ -282,9 +279,6 @@ async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, dele expect(intakeRegistry.telemetryErrorEvents).toEqual([]) validateRumFormat(intakeRegistry.rumEvents) withBrowserLogs((logs) => { - logs.forEach((browserLog) => { - log(`Browser ${browserLog.source}: ${browserLog.level} ${browserLog.message}`) - }) expect(logs.filter((log) => log.level === 'error')).toEqual([]) }) } diff --git a/test/e2e/lib/framework/httpServers.ts b/test/e2e/lib/framework/httpServers.ts index f05c5106c4..4c8e250e61 100644 --- a/test/e2e/lib/framework/httpServers.ts +++ b/test/e2e/lib/framework/httpServers.ts @@ -1,7 +1,6 @@ import * as http from 'http' import type { AddressInfo } from 'net' import { getIp } from '../../../envUtils' -import { log } from './logger' const MAX_SERVER_CREATION_RETRY = 5 // Not all port are available with BrowserStack, see https://www.browserstack.com/question/664 @@ -57,17 +56,6 @@ async function createServer(): Promise> { } }) - server.on('request', (req: http.IncomingMessage, res: http.ServerResponse) => { - let body = '' - req.on('data', (chunk) => { - body += chunk - }) - res.on('close', () => { - const requestUrl = `${req.headers.host!}${req.url!}` - log(`${req.method!} ${requestUrl} ${res.statusCode}${body ? `\n${body}` : ''}`) - }) - }) - return { bindServerApp(newServerApp: App) { serverApp = newServerApp diff --git a/test/e2e/lib/framework/logger.ts b/test/e2e/lib/framework/logger.ts deleted file mode 100644 index d82fa34a52..0000000000 --- a/test/e2e/lib/framework/logger.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as fs from 'fs' -import { inspect } from 'util' - -const logsPath = undefined // (browser.options as WebdriverIO.Config & { logsPath: string }).logsPath -const stream: { write(s: string): void } = logsPath - ? fs.createWriteStream(logsPath, { flags: 'a' }) - : { write: () => undefined } // TODO: why do we need to log to stdout? - -export function log(...args: any[]) { - const prefix = `[${process.pid}] ${new Date().toISOString()}` - stream.write(`${prefix}: ${formatArgs(args)}\n`) -} - -function formatArgs(args: any[]) { - return args.map((arg) => (typeof arg === 'string' ? arg : inspect(arg))).join(' ') -} From 97c308f4354d49c60449cbb72b59aa2a8be2a54b Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 30 Jan 2025 08:15:54 +0100 Subject: [PATCH 46/68] =?UTF-8?q?=F0=9F=91=8C=20improve=20test:e2e=20workf?= =?UTF-8?q?low?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- test/e2e/playwright.local.config.ts | 29 ++++++++++++----------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 38b51a33cd..09afcdf0db 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "test:script": "node --test --experimental-test-module-mocks './scripts/**/*.spec.*'", "test:unit:watch": "yarn test:unit --no-single-run", "test:unit:bs": "node ./scripts/test/bs-wrapper.js karma start test/unit/karma.bs.conf.js", - "test:e2e": "playwright test --config test/e2e/playwright.local.config.ts", + "test:e2e": "playwright test --config test/e2e/playwright.local.config.ts --project chromium", "test:e2e:bs": "node ./scripts/test/bs-wrapper.js playwright test --config test/e2e/playwright.bs.config.ts", "test:e2e:developer-extension": "yarn build && wdio test/e2e/wdio.developer-extension.conf.ts", "test:compat:tsc": "scripts/cli check_typescript_compatibility", diff --git a/test/e2e/playwright.local.config.ts b/test/e2e/playwright.local.config.ts index 8bdf3a2b3c..d0b3e8b2ac 100644 --- a/test/e2e/playwright.local.config.ts +++ b/test/e2e/playwright.local.config.ts @@ -6,25 +6,20 @@ const projects = [ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'android', + use: { ...devices['Pixel 7'] }, + }, ] -if (!process.env.CI) { - projects.push( - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - { - name: 'android', - use: { ...devices['Pixel 7'] }, - } - ) -} - // eslint-disable-next-line import/no-default-export export default defineConfig({ ...baseConfig, From 7a042c0f41262f0cb2db13dbc0ff5e47a03567c4 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 30 Jan 2025 09:20:04 +0100 Subject: [PATCH 47/68] =?UTF-8?q?=F0=9F=91=B7=20add=20DD=5FTAG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 4 ++++ test/e2e/scenario/rum/sessions.scenario.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index d11a3237b6..d747f9ade6 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -189,6 +189,10 @@ function declareTestsForSetups( function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { test(title, async ({ page, context, browserName }) => { const title = test.info().titlePath.join(' > ') + test.info().annotations.push({ + type: 'dd_tags[test.browser]', + description: browserName, + }) setupOptions.context.test_name = title diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 477d209ddf..e8218d7332 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -114,7 +114,7 @@ test.describe('rum sessions', () => { expect(intakeRegistry.rumActionEvents).toHaveLength(0) }) - createTest('after calling stopSession(), a user interaction starts a new session @known-flacky') + createTest('after calling stopSession(), a user interaction starts a new session @flaky') .withRum() .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { await page.evaluate(() => { @@ -122,7 +122,7 @@ test.describe('rum sessions', () => { }) test.info().annotations.push({ - type: 'known-flacky', + type: 'dd_tags[test.flaky]', description: 'This test is known to be flacky, especially in FF', }) From a23a419ac1f9dd144331f404f9cc84a0ff1c0874 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 30 Jan 2025 14:31:51 +0100 Subject: [PATCH 48/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20firefox=20issue=20pr?= =?UTF-8?q?operly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/helpers/browser.ts | 73 ++++++------------------------ test/e2e/scenario/logs.scenario.ts | 24 ++++------ 2 files changed, 22 insertions(+), 75 deletions(-) diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 7cd4775cef..79a9d33f91 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -1,63 +1,5 @@ -// import * as os from 'os' -// import { url } from 'inspector' -// import { resolve } from 'dns' import type { BrowserContext, Page } from '@playwright/test' - -// To keep tests sane, ensure we got a fixed list of possible platforms and browser names. -// const validPlatformNames = ['windows', 'macos', 'linux', 'ios', 'android'] as const -// const validBrowserNames = ['edge', 'safari', 'chrome', 'firefox'] as const - -// export function getBrowserName(): (typeof validBrowserNames)[number] { -// const capabilities = browser.capabilities - -// // Look for the browser name in capabilities. It should always be there as long as we don't change -// // the browser capabilities format. -// if (!('browserName' in capabilities) || typeof capabilities.browserName !== 'string') { -// throw new Error("Can't get browser name (no browser name)") -// } -// let browserName = capabilities.browserName.toLowerCase() -// if (browserName === 'msedge') { -// browserName = 'edge' -// } else if (browserName === 'chrome-headless-shell') { -// browserName = 'chrome' -// } -// if (!includes(validBrowserNames, browserName)) { -// throw new Error(`Can't get browser name (invalid browser name ${browserName})`) -// } - -// return browserName -// } - -// export function getPlatformName(): (typeof validPlatformNames)[number] { -// const capabilities = browser.capabilities - -// let platformName: string -// if ('bstack:options' in capabilities && capabilities['bstack:options']) { -// // Look for the platform name in browserstack options. It might not be always there, for example -// // when we run the test locally. This should be adjusted when we are changing the browser -// // capabilities format. -// platformName = (capabilities['bstack:options'] as any).os -// } else { -// // The test is run locally, use the local os name -// platformName = os.type() -// } - -// platformName = platformName.toLowerCase() -// if (/^(mac ?os|os ?x|mac ?os ?x|darwin)$/.test(platformName)) { -// platformName = 'macos' -// } else if (platformName === 'windows_nt') { -// platformName = 'windows' -// } -// if (!includes(validPlatformNames, platformName)) { -// throw new Error(`Can't get platform name (invalid platform name ${platformName})`) -// } - -// return platformName -// } - -// function includes(list: readonly T[], item: unknown): item is T { -// return list.includes(item as any) -// } +import { test } from '@playwright/test' export interface BrowserLog { level: 'log' | 'debug' | 'info' | 'error' | 'warning' @@ -74,7 +16,18 @@ export class BrowserLogsManager { } get() { - return this.logs.filter((log) => !log.message.includes('Ignoring unsupported entryTypes: longtask')) + const filteredLogs = this.logs.filter((log) => !log.message.includes('Ignoring unsupported entryTypes: longtask')) + + if (filteredLogs.length !== this.logs.length) { + // FIXME: fix this at the perfomance observer level as it is visible to customers + // It used to pass before because it was only happening in Firefox but wdio io did not support console logs for FF + test.info().annotations.push({ + type: 'dd_tags[test.fixme]', + description: 'Unnexpected Console log message: "Ignoring unsupported entryTypes: longtask"', + }) + } + + return filteredLogs } clear() { diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index db88762a0e..eb2e3e96cf 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -94,19 +94,10 @@ test.describe('logs', () => { }) }) - createTest('keep only the first bytes of the response @fixme') + createTest('keep only the first bytes of the response') .withLogs({ forwardErrorsToLogs: true }) .run(async ({ intakeRegistry, baseUrl, servers, flushEvents, page, withBrowserLogs, browserName }) => { - await page.evaluate( - () => - new Promise((resolve) => { - fetch('/throw-large-response') - .catch(console.log) - .finally(() => resolve()) - }) - ) - - test.fixme(browserName.includes('firefox'), 'This does not pass in FF') + await page.evaluate(() => fetch('/throw-large-response')) await flushEvents() expect(intakeRegistry.logsEvents).toHaveLength(1) @@ -123,10 +114,13 @@ test.describe('logs', () => { ) withBrowserLogs((browserLogs) => { - // Some browser report two errors: - // * the server responded with a status of 500 - // * canceling the body stream is reported as a network error (net::ERR_FAILED) - expect(browserLogs.length).toBeGreaterThanOrEqual(1) + if (browserName.includes('firefox')) { + // Firefox does not report the error message + expect(browserLogs).toHaveLength(0) + } else { + expect(browserLogs).toHaveLength(1) + expect(browserLogs[0].message).toContain('the server responded with a status of 500') + } }) }) From 39ef76d57004fca3ab20760b954ef86ee2efde4e Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 30 Jan 2025 14:33:06 +0100 Subject: [PATCH 49/68] =?UTF-8?q?=F0=9F=91=B7=20fix=20and=20add=20more=20t?= =?UTF-8?q?ags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/browsers.conf.js | 14 +++++----- test/e2e/lib/framework/createTest.ts | 17 ++++++----- test/e2e/lib/helpers/buildDdTags.ts | 29 +++++++++++++++++++ test/e2e/lib/helpers/playwright.ts | 42 ++++++++++++++++++++++++++++ test/e2e/playwright.bs.config.ts | 32 ++------------------- 5 files changed, 89 insertions(+), 45 deletions(-) create mode 100644 test/e2e/lib/helpers/buildDdTags.ts create mode 100644 test/e2e/lib/helpers/playwright.ts diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index 3bd96b8e11..b32332d49e 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -18,13 +18,13 @@ const browserConfigurations = [ os: 'Windows', osVersion: '11', }, - // { - // sessionName: 'Safari desktop', - // name: 'playwright-webkit', - // version: '15.4', - // os: 'OS X', - // osVersion: 'Big Sur', - // }, + { + sessionName: 'Safari desktop', + name: 'playwright-webkit', + version: '18.2', + os: 'OS X', + osVersion: 'Sequoia', + }, // { // sessionName: 'Chrome mobile', // name: 'chrome', diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index d747f9ade6..79e5d036b6 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,13 +1,15 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { DefaultPrivacyLevel } from '@datadog/browser-rum' -import type { BrowserContext, Page } from '@playwright/test' +import type { BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' import { test, expect } from '@playwright/test' +import { builDdTags } from '../helpers/buildDdTags' import { getRunId } from '../../../envUtils' import type { BrowserLog } from '../helpers/browser' import { BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' import { APPLICATION_ID, CLIENT_TOKEN } from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' +import type { BrowserConfiguration } from '../../../browsers.conf' import { IntakeRegistry } from './intakeRegistry' import { flushEvents } from './flushEvents' import type { Servers } from './httpServers' @@ -51,7 +53,7 @@ interface TestContext { servers: Servers page: Page browserContext: BrowserContext - browserName: 'chromium' | 'firefox' | 'webkit' + browserName: PlaywrightWorkerOptions['browserName'] withBrowserLogs: (cb: (logs: BrowserLog[]) => void) => void flushBrowserLogs: () => void flushEvents: () => Promise @@ -188,12 +190,9 @@ function declareTestsForSetups( function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { test(title, async ({ page, context, browserName }) => { - const title = test.info().titlePath.join(' > ') - test.info().annotations.push({ - type: 'dd_tags[test.browser]', - description: browserName, - }) + test.info().annotations.push(...builDdTags(browserName, test.info().project.metadata as BrowserConfiguration)) + const title = test.info().titlePath.join(' > ') setupOptions.context.test_name = title const servers = await getTestServers() @@ -280,9 +279,9 @@ async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, dele return } - expect(intakeRegistry.telemetryErrorEvents).toEqual([]) + expect(intakeRegistry.telemetryErrorEvents).toHaveLength(0) validateRumFormat(intakeRegistry.rumEvents) withBrowserLogs((logs) => { - expect(logs.filter((log) => log.level === 'error')).toEqual([]) + expect(logs.filter((log) => log.level === 'error')).toHaveLength(0) }) } diff --git a/test/e2e/lib/helpers/buildDdTags.ts b/test/e2e/lib/helpers/buildDdTags.ts new file mode 100644 index 0000000000..9bc8dd55e0 --- /dev/null +++ b/test/e2e/lib/helpers/buildDdTags.ts @@ -0,0 +1,29 @@ +import type { PlaywrightWorkerOptions, TestInfo } from '@playwright/test' +import type { BrowserConfiguration } from '../../../browsers.conf' + +export function builDdTags( + browserName: PlaywrightWorkerOptions['browserName'], + metadata: BrowserConfiguration | Record +) { + const tags: TestInfo['annotations'] = [ + { + type: 'dd_tags[test.browserName]', + description: browserName, + }, + ] + + for (let [tag, value] of Object.entries(metadata)) { + if (tag === 'name') { + tag = 'browser' + } else if (tag === 'version') { + tag = 'browserVersion' + } + + tags.push({ + type: `dd_tags[test.${tag}]`, + description: value, + }) + } + + return tags +} diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts new file mode 100644 index 0000000000..05e04ca229 --- /dev/null +++ b/test/e2e/lib/helpers/playwright.ts @@ -0,0 +1,42 @@ +import type { PlaywrightWorkerOptions } from '@playwright/test' +import type { BrowserConfiguration } from '../../../browsers.conf' +import { getBuildInfos } from '../../../envUtils' + +export function getBrowserName(name: string): PlaywrightWorkerOptions['browserName'] { + if (name.includes('firefox')) { + return 'firefox' + } + + if (name.includes('webkit')) { + return 'webkit' + } + + return 'chromium' +} + +export function getEncodedCapabilities(configuration: BrowserConfiguration) { + return encodeURIComponent(JSON.stringify(getCapabilities(configuration))) +} + +// see: https://www.browserstack.com/docs/automate/playwright/playwright-capabilities +function getCapabilities(configuration: BrowserConfiguration) { + return { + os: configuration.os, + os_version: configuration.osVersion, + browser: configuration.name, + browser_version: configuration.version, + ...(configuration.device ? { deviceName: configuration.device } : {}), + 'browserstack.username': process.env.BS_USERNAME, + 'browserstack.accessKey': process.env.BS_ACCESS_KEY, + project: 'browser sdk e2e', + build: getBuildInfos(), + name: configuration.sessionName, + 'browserstack.local': 'true', + 'browserstack.playwrightVersion': '1.latest', + 'client.playwrightVersion': '1.latest', + 'browserstack.debug': 'false', + 'browserstack.console': 'info', + 'browserstack.networkLogs': 'false', + 'browserstack.interactiveDebugging': 'false', + } +} diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index f05d5bb9bd..949eadcae1 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -1,6 +1,5 @@ import { defineConfig } from '@playwright/test' -import type { BrowserConfiguration } from '../browsers.conf' -import { getBuildInfos } from '../envUtils' +import { getBrowserName, getEncodedCapabilities } from './lib/helpers/playwright' import { config as baseConfig } from './playwright.base.config' import { browserConfigurations } from './browsers.conf' @@ -12,37 +11,12 @@ export default defineConfig({ maxFailures: process.env.CI ? 1 : 0, projects: browserConfigurations.map((configuration) => ({ name: configuration.name, + metadata: configuration, use: { + browserName: getBrowserName(configuration.name), connectOptions: { wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, }, }, })), }) - -function getEncodedCapabilities(configuration: BrowserConfiguration) { - return encodeURIComponent(JSON.stringify(getCapabilities(configuration))) -} - -// see: https://www.browserstack.com/docs/automate/playwright/playwright-capabilities -function getCapabilities(configuration: BrowserConfiguration) { - return { - os: configuration.os, - os_version: configuration.osVersion, - browser: configuration.name, - browser_version: configuration.version, - device: configuration.device, - 'browserstack.username': process.env.BS_USERNAME, - 'browserstack.accessKey': process.env.BS_ACCESS_KEY, - project: 'browser sdk e2e', - build: getBuildInfos(), - name: configuration.sessionName, - 'browserstack.local': 'true', - 'browserstack.playwrightVersion': '1.latest', - 'client.playwrightVersion': '1.latest', - 'browserstack.debug': 'false', - 'browserstack.console': 'info', - 'browserstack.networkLogs': 'false', - 'browserstack.interactiveDebugging': 'false', - } -} From 5ec0b439c479673090294edebb8dcf2b3f217ec2 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 30 Jan 2025 18:29:04 +0100 Subject: [PATCH 50/68] =?UTF-8?q?=F0=9F=91=B7=20add=20safari=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/browsers.conf.js | 6 +++--- test/e2e/lib/helpers/playwright.ts | 8 ++++---- test/e2e/playwright.bs.config.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index b32332d49e..affc011f3e 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -14,16 +14,16 @@ const browserConfigurations = [ { sessionName: 'Firefox', name: 'playwright-firefox', - version: '132', + version: '119', os: 'Windows', osVersion: '11', }, { sessionName: 'Safari desktop', name: 'playwright-webkit', - version: '18.2', + version: '17.4', os: 'OS X', - osVersion: 'Sequoia', + osVersion: 'Big Sur', }, // { // sessionName: 'Chrome mobile', diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts index 05e04ca229..8eba56854d 100644 --- a/test/e2e/lib/helpers/playwright.ts +++ b/test/e2e/lib/helpers/playwright.ts @@ -31,12 +31,12 @@ function getCapabilities(configuration: BrowserConfiguration) { project: 'browser sdk e2e', build: getBuildInfos(), name: configuration.sessionName, - 'browserstack.local': 'true', + 'browserstack.local': true, 'browserstack.playwrightVersion': '1.latest', 'client.playwrightVersion': '1.latest', - 'browserstack.debug': 'false', + 'browserstack.debug': false, 'browserstack.console': 'info', - 'browserstack.networkLogs': 'false', - 'browserstack.interactiveDebugging': 'false', + 'browserstack.networkLogs': false, + 'browserstack.interactiveDebugging': false, } } diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index 949eadcae1..72f5ca49a4 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ ...baseConfig, workers: 5, // BrowserStack has a limit of 5 parallel sessions testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack - maxFailures: process.env.CI ? 1 : 0, + // maxFailures: process.env.CI ? 1 : 0, projects: browserConfigurations.map((configuration) => ({ name: configuration.name, metadata: configuration, From 155a83bd9e4853886bc69536137b3abf2b7f6dde Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 07:39:12 +0100 Subject: [PATCH 51/68] =?UTF-8?q?=F0=9F=91=8C=20filter=20out=20all=20unsup?= =?UTF-8?q?ported=20entryTypes=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/helpers/browser.ts | 4 ++-- test/e2e/scenario/recorder/recorder.scenario.ts | 2 +- test/e2e/scenario/rum/actions.scenario.ts | 14 +++++++------- test/e2e/scenario/trackingConsent.scenario.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 79a9d33f91..7fc3e9f54d 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -16,14 +16,14 @@ export class BrowserLogsManager { } get() { - const filteredLogs = this.logs.filter((log) => !log.message.includes('Ignoring unsupported entryTypes: longtask')) + const filteredLogs = this.logs.filter((log) => !log.message.includes('Ignoring unsupported entryTypes: ')) if (filteredLogs.length !== this.logs.length) { // FIXME: fix this at the perfomance observer level as it is visible to customers // It used to pass before because it was only happening in Firefox but wdio io did not support console logs for FF test.info().annotations.push({ type: 'dd_tags[test.fixme]', - description: 'Unnexpected Console log message: "Ignoring unsupported entryTypes: longtask"', + description: 'Unnexpected Console log message: "Ignoring unsupported entryTypes: *"', }) } diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 36d6ec2f8d..7d1c83236e 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -286,7 +286,7 @@ test.describe('recorder', () => { expect(intakeRegistry.replaySegments).toHaveLength(1) const segment = intakeRegistry.replaySegments[0] - expect(findAllIncrementalSnapshots(segment, IncrementalSource.Mutation)).toEqual([]) + expect(findAllIncrementalSnapshots(segment, IncrementalSource.Mutation)).toHaveLength(0) }) createTest('record DOM node movement 1') diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 05d87436bf..4be21c09ed 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -238,7 +238,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(1) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('do not consider a click to change the value of a "range" input as "dead_click"') @@ -251,7 +251,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(1) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('consider a click on an already checked "radio" input as "dead_click"') @@ -277,7 +277,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(1) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('do not consider clicks leading to scrolls as "dead_click"') @@ -301,7 +301,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(1) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('do not consider clicks leading to scrolls as "rage_click"') @@ -327,7 +327,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(3) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('do not consider a click that open a new window as "dead_click"') @@ -349,7 +349,7 @@ test.describe('action collection', () => { const actionEvents = intakeRegistry.rumActionEvents expect(actionEvents).toHaveLength(1) - expect(actionEvents[0].action.frustration!.type).toEqual([]) + expect(actionEvents[0].action.frustration!.type).toHaveLength(0) }) createTest('collect a "rage click"') @@ -443,7 +443,7 @@ test.describe('action collection', () => { .run(({ withBrowserLogs }) => { withBrowserLogs((logs) => { // A failing test would have a log with message "Uncaught RangeError: Maximum call stack size exceeded" - expect(logs).toEqual([]) + expect(logs).toHaveLength(0) }) }) }) diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index b304bb4a25..8254b52f1c 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -38,7 +38,7 @@ test.describe('tracking consent', () => { await flushEvents() - expect(intakeRegistry.rumActionEvents).toEqual([]) + expect(intakeRegistry.rumActionEvents).toHaveLength(0) expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') }) From 07fc44b6f02807e8d7ef9badc373f96a97199f9d Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 07:46:46 +0100 Subject: [PATCH 52/68] =?UTF-8?q?=F0=9F=91=8C=20improve=20error=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/logs.scenario.ts | 4 ++-- test/e2e/scenario/rum/actions.scenario.ts | 4 ++-- test/e2e/scenario/rum/errors.scenario.ts | 12 ++++++------ test/e2e/scenario/rum/init.scenario.ts | 2 ++ test/e2e/scenario/rum/resources.scenario.ts | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/test/e2e/scenario/logs.scenario.ts b/test/e2e/scenario/logs.scenario.ts index eb2e3e96cf..903e7ea36a 100644 --- a/test/e2e/scenario/logs.scenario.ts +++ b/test/e2e/scenario/logs.scenario.ts @@ -45,7 +45,7 @@ test.describe('logs', () => { expect(intakeRegistry.logsEvents).toHaveLength(1) expect(intakeRegistry.logsEvents[0].message).toBe('oh snap') withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -155,7 +155,7 @@ test.describe('logs', () => { flushBrowserLogs() await flushEvents() - expect(intakeRegistry.logsEvents.length).toEqual(2) + expect(intakeRegistry.logsEvents).toHaveLength(2) const unreachableRequest = intakeRegistry.logsEvents.find((log) => log.http!.url.includes('/unreachable'))! const throwRequest = intakeRegistry.logsEvents.find((log) => log.http!.url.includes('/throw'))! diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 4be21c09ed..632d4d1d27 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -209,7 +209,7 @@ test.describe('action collection', () => { expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(1) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -411,7 +411,7 @@ test.describe('action collection', () => { expect(intakeRegistry.rumViewEvents[0].view.frustration!.count).toBe(2) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 2a58b3e1a7..0e7af431b9 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -36,7 +36,7 @@ test.describe('rum errors', () => { handling: 'handled', }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -57,7 +57,7 @@ test.describe('rum errors', () => { handling: 'handled', }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -77,7 +77,7 @@ test.describe('rum errors', () => { handling: 'unhandled', }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -97,7 +97,7 @@ test.describe('rum errors', () => { handling: 'unhandled', }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) @@ -118,7 +118,7 @@ test.describe('rum errors', () => { handling: 'handled', }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(0) + expect(browserLogs).toHaveLength(0) }) }) @@ -158,7 +158,7 @@ test.describe('rum errors', () => { }, }) withBrowserLogs((browserLogs) => { - expect(browserLogs.length).toEqual(1) + expect(browserLogs).toHaveLength(1) }) }) }) diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 4210c305bd..3bc081cdc2 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -10,6 +10,8 @@ test.describe('API calls and events around init', () => { ;(window.DD_RUM! as unknown as { init(): void }).init() }) .run(({ withBrowserLogs }) => { + expect([1, 2, 3]).toHaveLength(1) + withBrowserLogs((logs) => { expect(logs).toHaveLength(1) expect(logs[0].message).toEqual(expect.stringContaining('Datadog Browser SDK')) diff --git a/test/e2e/scenario/rum/resources.scenario.ts b/test/e2e/scenario/rum/resources.scenario.ts index ce51281ade..6a7df00ff8 100644 --- a/test/e2e/scenario/rum/resources.scenario.ts +++ b/test/e2e/scenario/rum/resources.scenario.ts @@ -288,7 +288,7 @@ test.describe('rum resources', () => { await flushEvents() const resourceEvents = intakeRegistry.rumResourceEvents.filter((event) => event.resource.type === 'xhr') - expect(resourceEvents.length).toEqual(2) + expect(resourceEvents).toHaveLength(2) expect(intakeRegistry.rumErrorEvents).toHaveLength(0) expect(resourceEvents[0].resource.url).toContain('/ok?duration=100&call=1') expect(resourceEvents[0].resource.status_code).toEqual(200) From 342e3344f086049b87f8fa03dfe2a7723409c188 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 08:22:56 +0100 Subject: [PATCH 53/68] =?UTF-8?q?=F0=9F=91=B7=20add=20eslint-prefer-cont?= =?UTF-8?q?=20destructuring=20all=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 1 + packages/rum/src/boot/recorderApi.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index f0f5a10f51..7c46c9c9e0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -112,6 +112,7 @@ export default tseslint.config( 'prefer-rest-params': 'off', 'prefer-template': 'error', 'prefer-object-spread': 'error', + 'prefer-const': ['error', { destructuring: 'all' }], quotes: ['error', 'single', { avoidEscape: true }], radix: 'error', 'require-await': 'error', diff --git a/packages/rum/src/boot/recorderApi.ts b/packages/rum/src/boot/recorderApi.ts index c562d18650..e16bcfaf79 100644 --- a/packages/rum/src/boot/recorderApi.ts +++ b/packages/rum/src/boot/recorderApi.ts @@ -42,7 +42,6 @@ export function makeRecorderApi( } } - // eslint-disable-next-line prefer-const let { strategy, shouldStartImmediately } = createPreStartStrategy() return { From 2df1d26cab56a500f45c36f72dc62943891b33dc Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 09:11:43 +0100 Subject: [PATCH 54/68] =?UTF-8?q?=F0=9F=91=8C=20split=20tearDown=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 16 +++++++--------- test/e2e/scenario/rum/init.scenario.ts | 2 -- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 79e5d036b6..22c01e61fa 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -209,6 +209,7 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa try { await runner(testContext) + tearDownPassedTest(testContext) } finally { await tearDownTest(testContext) } @@ -270,18 +271,15 @@ async function setUpTest(browserLogsManager: BrowserLogsManager, { baseUrl, page await waitForServersIdle() } -async function tearDownTest({ intakeRegistry, withBrowserLogs, flushEvents, deleteAllCookies }: TestContext) { - await flushEvents() - await deleteAllCookies() - - if (test.info().expectedStatus === 'skipped') { - // ignore following expectations if test was skipped - return - } - +function tearDownPassedTest({ intakeRegistry, withBrowserLogs }: TestContext) { expect(intakeRegistry.telemetryErrorEvents).toHaveLength(0) validateRumFormat(intakeRegistry.rumEvents) withBrowserLogs((logs) => { expect(logs.filter((log) => log.level === 'error')).toHaveLength(0) }) } + +async function tearDownTest({ flushEvents, deleteAllCookies }: TestContext) { + await flushEvents() + await deleteAllCookies() +} diff --git a/test/e2e/scenario/rum/init.scenario.ts b/test/e2e/scenario/rum/init.scenario.ts index 3bc081cdc2..4210c305bd 100644 --- a/test/e2e/scenario/rum/init.scenario.ts +++ b/test/e2e/scenario/rum/init.scenario.ts @@ -10,8 +10,6 @@ test.describe('API calls and events around init', () => { ;(window.DD_RUM! as unknown as { init(): void }).init() }) .run(({ withBrowserLogs }) => { - expect([1, 2, 3]).toHaveLength(1) - withBrowserLogs((logs) => { expect(logs).toHaveLength(1) expect(logs[0].message).toEqual(expect.stringContaining('Datadog Browser SDK')) From 34f7b8ae180554dc65aeafeeb65ed0fdba627b95 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 09:24:58 +0100 Subject: [PATCH 55/68] =?UTF-8?q?Revert=20"=F0=9F=91=B7=20add=20eslint-pre?= =?UTF-8?q?fer-cont=20destructuring=20all=20option"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 342e3344f086049b87f8fa03dfe2a7723409c188. --- eslint.config.mjs | 1 - packages/rum/src/boot/recorderApi.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index e6a6614478..fe5374c4c2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -112,7 +112,6 @@ export default tseslint.config( 'prefer-rest-params': 'off', 'prefer-template': 'error', 'prefer-object-spread': 'error', - 'prefer-const': ['error', { destructuring: 'all' }], quotes: ['error', 'single', { avoidEscape: true }], radix: 'error', 'require-await': 'error', diff --git a/packages/rum/src/boot/recorderApi.ts b/packages/rum/src/boot/recorderApi.ts index e16bcfaf79..c562d18650 100644 --- a/packages/rum/src/boot/recorderApi.ts +++ b/packages/rum/src/boot/recorderApi.ts @@ -42,6 +42,7 @@ export function makeRecorderApi( } } + // eslint-disable-next-line prefer-const let { strategy, shouldStartImmediately } = createPreStartStrategy() return { From d198f7d0ddc018e4aa8aabe5259ecbb389d728b9 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 09:25:58 +0100 Subject: [PATCH 56/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20linting=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/helpers/buildDdTags.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/lib/helpers/buildDdTags.ts b/test/e2e/lib/helpers/buildDdTags.ts index 9bc8dd55e0..96fb43cf27 100644 --- a/test/e2e/lib/helpers/buildDdTags.ts +++ b/test/e2e/lib/helpers/buildDdTags.ts @@ -12,6 +12,7 @@ export function builDdTags( }, ] + // eslint-disable-next-line prefer-const for (let [tag, value] of Object.entries(metadata)) { if (tag === 'name') { tag = 'browser' From 195bda39d55dfe6ed3999cda9d36f61655088f77 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 31 Jan 2025 17:38:20 +0100 Subject: [PATCH 57/68] =?UTF-8?q?=F0=9F=91=8C=20Improve=20dd=20tags=20mana?= =?UTF-8?q?gements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 16 ++++++++++-- test/e2e/lib/helpers/browser.ts | 7 ++--- test/e2e/lib/helpers/buildDdTags.ts | 30 ---------------------- test/e2e/lib/helpers/tags.ts | 27 +++++++++++++++++++ test/e2e/scenario/rum/sessions.scenario.ts | 6 ++--- 5 files changed, 45 insertions(+), 41 deletions(-) delete mode 100644 test/e2e/lib/helpers/buildDdTags.ts create mode 100644 test/e2e/lib/helpers/tags.ts diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 22c01e61fa..43f5eccc0b 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -3,7 +3,8 @@ import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { DefaultPrivacyLevel } from '@datadog/browser-rum' import type { BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' import { test, expect } from '@playwright/test' -import { builDdTags } from '../helpers/buildDdTags' +import type { Tag } from '../helpers/tags' +import { addTag, addBrowserConfigurationTags } from '../helpers/tags' import { getRunId } from '../../../envUtils' import type { BrowserLog } from '../helpers/browser' import { BrowserLogsManager, deleteAllCookies, sendXhr } from '../helpers/browser' @@ -190,7 +191,8 @@ function declareTestsForSetups( function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { test(title, async ({ page, context, browserName }) => { - test.info().annotations.push(...builDdTags(browserName, test.info().project.metadata as BrowserConfiguration)) + addTag('browserName' as any as Tag, browserName) + addBrowserConfigurationTags(test.info().project.metadata as BrowserConfiguration) const title = test.info().titlePath.join(' > ') setupOptions.context.test_name = title @@ -282,4 +284,14 @@ function tearDownPassedTest({ intakeRegistry, withBrowserLogs }: TestContext) { async function tearDownTest({ flushEvents, deleteAllCookies }: TestContext) { await flushEvents() await deleteAllCookies() + + const skipReason = test.info().annotations.find((annotation) => annotation.type === 'skip')?.description + if (skipReason) { + addTag('skip', skipReason) + } + + const fixmeReason = test.info().annotations.find((annotation) => annotation.type === 'fixme')?.description + if (fixmeReason) { + addTag('fixme', fixmeReason) + } } diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 7fc3e9f54d..581339e0d9 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -1,5 +1,5 @@ import type { BrowserContext, Page } from '@playwright/test' -import { test } from '@playwright/test' +import { addTag } from './tags' export interface BrowserLog { level: 'log' | 'debug' | 'info' | 'error' | 'warning' @@ -21,10 +21,7 @@ export class BrowserLogsManager { if (filteredLogs.length !== this.logs.length) { // FIXME: fix this at the perfomance observer level as it is visible to customers // It used to pass before because it was only happening in Firefox but wdio io did not support console logs for FF - test.info().annotations.push({ - type: 'dd_tags[test.fixme]', - description: 'Unnexpected Console log message: "Ignoring unsupported entryTypes: *"', - }) + addTag('fixme', 'Unnexpected Console log message: "Ignoring unsupported entryTypes: *"') } return filteredLogs diff --git a/test/e2e/lib/helpers/buildDdTags.ts b/test/e2e/lib/helpers/buildDdTags.ts deleted file mode 100644 index 96fb43cf27..0000000000 --- a/test/e2e/lib/helpers/buildDdTags.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { PlaywrightWorkerOptions, TestInfo } from '@playwright/test' -import type { BrowserConfiguration } from '../../../browsers.conf' - -export function builDdTags( - browserName: PlaywrightWorkerOptions['browserName'], - metadata: BrowserConfiguration | Record -) { - const tags: TestInfo['annotations'] = [ - { - type: 'dd_tags[test.browserName]', - description: browserName, - }, - ] - - // eslint-disable-next-line prefer-const - for (let [tag, value] of Object.entries(metadata)) { - if (tag === 'name') { - tag = 'browser' - } else if (tag === 'version') { - tag = 'browserVersion' - } - - tags.push({ - type: `dd_tags[test.${tag}]`, - description: value, - }) - } - - return tags -} diff --git a/test/e2e/lib/helpers/tags.ts b/test/e2e/lib/helpers/tags.ts new file mode 100644 index 0000000000..499d56ae99 --- /dev/null +++ b/test/e2e/lib/helpers/tags.ts @@ -0,0 +1,27 @@ +import { test } from '@playwright/test' +import type { BrowserConfiguration } from '../../../browsers.conf' + +export type Tag = 'skip' | 'fixme' | 'flaky' + +export function addTag(name: Tag, value: string) { + test.info().annotations.push({ + type: `dd_tags[test.${name}]`, + description: value, + }) +} + +export function addBrowserConfigurationTags(metadata: BrowserConfiguration | Record) { + // eslint-disable-next-line prefer-const + for (let [tag, value] of Object.entries(metadata)) { + if (tag === 'name') { + tag = 'browser' + } else if (tag === 'version') { + tag = 'browserVersion' + } + + test.info().annotations.push({ + type: `dd_tags[test.${tag}]`, + description: value, + }) + } +} diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index e8218d7332..4f8a3a41dd 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -1,5 +1,6 @@ import { RecordType } from '@datadog/browser-rum/src/types' import { test, expect } from '@playwright/test' +import { addTag } from '../../lib/helpers/tags' import { expireSession, findSessionCookie, renewSession } from '../../lib/helpers/session' import { bundleSetup, createTest, waitForRequests } from '../../lib/framework' @@ -121,10 +122,7 @@ test.describe('rum sessions', () => { window.DD_RUM!.stopSession() }) - test.info().annotations.push({ - type: 'dd_tags[test.flaky]', - description: 'This test is known to be flacky, especially in FF', - }) + addTag('flaky', 'This test is known to be flacky, especially in FF') await page.locator('html').click() From 72d7d7f506150551c44ec70a91dfe40a8bbd4a3d Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 3 Feb 2025 09:52:45 +0100 Subject: [PATCH 58/68] =?UTF-8?q?=F0=9F=91=8C=20small=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/createTest.ts | 4 +--- test/e2e/lib/helpers/browser.ts | 2 -- test/e2e/scenario/rum/sessions.scenario.ts | 27 ++++++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 43f5eccc0b..501dd2bb8f 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -241,9 +241,7 @@ function createTestContext( browserLogsManager.clear() } }, - flushBrowserLogs: () => { - browserLogsManager.clear() - }, + flushBrowserLogs: () => browserLogsManager.clear(), flushEvents: () => flushEvents(page), deleteAllCookies: () => deleteAllCookies(browserContext), sendXhr: (url: string, headers?: string[][]) => sendXhr(page, url, headers), diff --git a/test/e2e/lib/helpers/browser.ts b/test/e2e/lib/helpers/browser.ts index 581339e0d9..f2a6753fc2 100644 --- a/test/e2e/lib/helpers/browser.ts +++ b/test/e2e/lib/helpers/browser.ts @@ -32,8 +32,6 @@ export class BrowserLogsManager { } } -// TODO, see if we can use the browser context to clear cookies or we should keep the previous hack -// wdio method does not work for some browsers export function deleteAllCookies(context: BrowserContext) { return context.clearCookies() } diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 4f8a3a41dd..e2def67f99 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -97,18 +97,21 @@ test.describe('rum sessions', () => { createTest('calling stopSession() stops the session') .withRum() .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { - await page.evaluate(() => { - window.DD_RUM!.stopSession() - setTimeout(() => { - // If called directly after `stopSession`, the action start time may be the same as the - // session end time. In this case, the sopped session is used, and the action is - // collected. - // We might want to improve this by having a strict comparison between the event start - // time and session end time. - window.DD_RUM!.addAction('foo') - // done() - }, 5) - }) + await page.evaluate( + () => + new Promise((resolve) => { + window.DD_RUM!.stopSession() + setTimeout(() => { + // If called directly after `stopSession`, the action start time may be the same as the + // session end time. In this case, the sopped session is used, and the action is + // collected. + // We might want to improve this by having a strict comparison between the event start + // time and session end time. + window.DD_RUM!.addAction('foo') + resolve() + }, 5) + }) + ) await flushEvents() expect((await findSessionCookie(browserContext))?.isExpired).toEqual('1') From 77f9052cd460e81f7a252ba2db1e8a77da09f1af Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 6 Feb 2025 13:59:08 +0100 Subject: [PATCH 59/68] =?UTF-8?q?=E2=9C=85=20add=20Google=20pixel=20pro=20?= =?UTF-8?q?6=20tests=20on=20browserStack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 1 + test/e2e/browsers.conf.js | 13 ++++++----- test/e2e/lib/framework/createTest.ts | 31 +++++++++++++++++++++------ test/e2e/lib/helpers/playwright.ts | 23 +++++++++++++++++--- test/e2e/playwright.bs.config.ts | 32 ++++++++++++++++++++-------- 5 files changed, 75 insertions(+), 25 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7cb5d71ed5..70409b8d78 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -293,6 +293,7 @@ e2e-bs: - yarn - yarn build - yarn build:app + - yarn playwright install chromium-headless-shell --with-deps - FORCE_COLOR=1 ./scripts/test/ci-bs.sh test:e2e after_script: - node ./scripts/test/export-test-result.js e2e-bs diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index affc011f3e..a6662f644a 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -25,13 +25,12 @@ const browserConfigurations = [ os: 'OS X', osVersion: 'Big Sur', }, - // { - // sessionName: 'Chrome mobile', - // name: 'chrome', - // os: 'android', - // osVersion: '12.0', - // device: 'Google Pixel 6 Pro', - // }, + { + sessionName: 'Chrome mobile', + name: 'chrome', + osVersion: '12.0', + device: 'Google Pixel 6 Pro', + }, ] module.exports = { diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 501dd2bb8f..75aba91fae 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,8 +1,9 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { DefaultPrivacyLevel } from '@datadog/browser-rum' -import type { BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' +import type { AndroidDevice, BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' import { test, expect } from '@playwright/test' +import { connectToAndroidDevice } from '../helpers/playwright' import type { Tag } from '../helpers/tags' import { addTag, addBrowserConfigurationTags } from '../helpers/tags' import { getRunId } from '../../../envUtils' @@ -190,9 +191,11 @@ function declareTestsForSetups( } function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { - test(title, async ({ page, context, browserName }) => { + test(title, async ({ page, context, browserName, playwright }) => { + const projectMetadata = test.info().project.metadata as BrowserConfiguration + addTag('browserName' as any as Tag, browserName) - addBrowserConfigurationTags(test.info().project.metadata as BrowserConfiguration) + addBrowserConfigurationTags(projectMetadata) const title = test.info().titlePath.join(' > ') setupOptions.context.test_name = title @@ -200,7 +203,18 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa const servers = await getTestServers() const browserLogs = new BrowserLogsManager() - const testContext = createTestContext(servers, page, context, browserLogs, browserName, setupOptions) + let _page = page + let _context = context + let _device: AndroidDevice | undefined + + if (projectMetadata.device) { + const { page, context, device } = await connectToAndroidDevice(playwright._android, projectMetadata) + _page = page + _context = context + _device = device + } + + const testContext = createTestContext(servers, _page, _context, browserLogs, browserName, setupOptions) servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry)) const setup = factory(setupOptions, servers) @@ -213,7 +227,7 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa await runner(testContext) tearDownPassedTest(testContext) } finally { - await tearDownTest(testContext) + await tearDownTest(testContext, _device) } }) } @@ -279,10 +293,15 @@ function tearDownPassedTest({ intakeRegistry, withBrowserLogs }: TestContext) { }) } -async function tearDownTest({ flushEvents, deleteAllCookies }: TestContext) { +async function tearDownTest({ flushEvents, deleteAllCookies, page }: TestContext, device?: AndroidDevice) { await flushEvents() await deleteAllCookies() + if (device) { + await page.close() + await device.close() + } + const skipReason = test.info().annotations.find((annotation) => annotation.type === 'skip')?.description if (skipReason) { addTag('skip', skipReason) diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts index 8eba56854d..4afa3854f3 100644 --- a/test/e2e/lib/helpers/playwright.ts +++ b/test/e2e/lib/helpers/playwright.ts @@ -1,4 +1,4 @@ -import type { PlaywrightWorkerOptions } from '@playwright/test' +import type { Android, PlaywrightWorkerOptions } from '@playwright/test' import type { BrowserConfiguration } from '../../../browsers.conf' import { getBuildInfos } from '../../../envUtils' @@ -20,12 +20,11 @@ export function getEncodedCapabilities(configuration: BrowserConfiguration) { // see: https://www.browserstack.com/docs/automate/playwright/playwright-capabilities function getCapabilities(configuration: BrowserConfiguration) { - return { + const capabilities: Record = { os: configuration.os, os_version: configuration.osVersion, browser: configuration.name, browser_version: configuration.version, - ...(configuration.device ? { deviceName: configuration.device } : {}), 'browserstack.username': process.env.BS_USERNAME, 'browserstack.accessKey': process.env.BS_ACCESS_KEY, project: 'browser sdk e2e', @@ -39,4 +38,22 @@ function getCapabilities(configuration: BrowserConfiguration) { 'browserstack.networkLogs': false, 'browserstack.interactiveDebugging': false, } + + if (configuration.device) { + capabilities.deviceName = configuration.device + capabilities.realMobile = true + } + + return capabilities +} + +export async function connectToAndroidDevice(android: Android, configuration: BrowserConfiguration) { + const device = await android.connect( + `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}` + ) + await device.shell('am force-stop com.android.chrome') + const context = await device.launchBrowser() + const page = await context.newPage() + + return { page, context, device } } diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index 72f5ca49a4..a940798a43 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -1,3 +1,4 @@ +import type { Project } from '@playwright/test' import { defineConfig } from '@playwright/test' import { getBrowserName, getEncodedCapabilities } from './lib/helpers/playwright' import { config as baseConfig } from './playwright.base.config' @@ -9,14 +10,27 @@ export default defineConfig({ workers: 5, // BrowserStack has a limit of 5 parallel sessions testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack // maxFailures: process.env.CI ? 1 : 0, - projects: browserConfigurations.map((configuration) => ({ - name: configuration.name, - metadata: configuration, - use: { - browserName: getBrowserName(configuration.name), - connectOptions: { - wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, + projects: browserConfigurations.map((configuration) => { + const project: Project = { + name: configuration.sessionName, + metadata: configuration, + } + + if (configuration.device) { + return { + ...project, + timeout: 60_000, + } + } + + return { + ...project, + use: { + browserName: getBrowserName(configuration.name), + connectOptions: { + wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, + }, }, - }, - })), + } + }), }) From 20d38d49dc1052f499f369bab7bf1ab1e1b30ac6 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 6 Feb 2025 15:38:01 +0100 Subject: [PATCH 60/68] =?UTF-8?q?=F0=9F=91=8C=20fix=20yarn=20lockfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0ba327fdeb..09d99bb4ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2121,7 +2121,7 @@ __metadata: dependencies: "@typescript-eslint/types": "npm:8.23.0" eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/b3f1412f550e35c0d7ae0410db616951116b365167539f9b85710d8bc2b36b322c5e637caee84cc1ae5df8f1d961880250d52ffdef352b31e5bdbef74ba6fea9 + checksum: 10c0/a406f78aa18b4efb2adf26e3a6ca48c9a6f2cc9545e083b50efaaf90f0a80d2bea79ceda51da1f109706d4138756b0978a323b9176c9a6a519e87168851e7e16 languageName: node linkType: hard @@ -11546,7 +11546,7 @@ __metadata: dependenciesMeta: bare-events: optional: true - checksum: 10c0/0a51ac2d73908a124a66480f12d5dc5ccbc630c4d98814963f745cbcad119801f621894e9179d0776885b1c9297fe799897f898838b2ee695c25f0a18555d863 + checksum: 10c0/f5017998a5b6360ba652599d20ef308c8c8ab0e26c8e5f624f0706f0ea12624e94fdf1ec18318124498529a1b106a1ab1c94a1b1e1ad6c2eec7cb9c8ac1b9198 languageName: node linkType: hard From 4a1de99318cc310dc3805b352ff2f3cecc899b18 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 7 Feb 2025 10:05:25 +0100 Subject: [PATCH 61/68] =?UTF-8?q?=F0=9F=91=8C=20remove=20unused=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index dfa56d675e..8adc5c0824 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "test:unit:bs": "node ./scripts/test/bs-wrapper.js karma start test/unit/karma.bs.conf.js", "test:e2e": "playwright test --config test/e2e/playwright.local.config.ts --project chromium", "test:e2e:bs": "node ./scripts/test/bs-wrapper.js playwright test --config test/e2e/playwright.bs.config.ts", - "test:e2e:developer-extension": "yarn build && wdio test/e2e/wdio.developer-extension.conf.ts", "test:compat:tsc": "scripts/cli check_typescript_compatibility", "test:compat:ssr": "scripts/cli check_server_side_rendering_compatibility", "rum-events-format:sync": "scripts/cli update_submodule && scripts/cli build_json2type && node scripts/generate-schema-types.js", From 6cc76ba54061ce12cd6435986cf7c4f3e008cdcf Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 7 Feb 2025 12:58:06 +0100 Subject: [PATCH 62/68] =?UTF-8?q?=F0=9F=91=8C=20remove=20flacky=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/helpers/tags.ts | 2 +- test/e2e/scenario/rum/sessions.scenario.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/e2e/lib/helpers/tags.ts b/test/e2e/lib/helpers/tags.ts index 499d56ae99..a26b16c0d8 100644 --- a/test/e2e/lib/helpers/tags.ts +++ b/test/e2e/lib/helpers/tags.ts @@ -1,7 +1,7 @@ import { test } from '@playwright/test' import type { BrowserConfiguration } from '../../../browsers.conf' -export type Tag = 'skip' | 'fixme' | 'flaky' +export type Tag = 'skip' | 'fixme' export function addTag(name: Tag, value: string) { test.info().annotations.push({ diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 9ccfc09acf..d74a5a7dfc 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -110,15 +110,13 @@ test.describe('rum sessions', () => { expect(intakeRegistry.rumActionEvents).toHaveLength(0) }) - createTest('after calling stopSession(), a user interaction starts a new session @flaky') + createTest('after calling stopSession(), a user interaction starts a new session') .withRum() .run(async ({ intakeRegistry, flushEvents, browserContext, page }) => { await page.evaluate(() => { window.DD_RUM!.stopSession() }) - addTag('flaky', 'This test is known to be flacky, especially in FF') - await page.locator('html').click() // The session is not created right away, let's wait until we see a cookie From 04ade7de7c52a2d133f52ec682da87890cd8ca5f Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 11 Feb 2025 09:25:56 +0100 Subject: [PATCH 63/68] =?UTF-8?q?=E2=9C=85=20viewport=20scenario=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scenario/recorder/viewports.scenario.ts | 83 ++++++------------- test/e2e/scenario/rum/sessions.scenario.ts | 1 - 2 files changed, 24 insertions(+), 60 deletions(-) diff --git a/test/e2e/scenario/recorder/viewports.scenario.ts b/test/e2e/scenario/recorder/viewports.scenario.ts index 7c056107b0..fa879c729c 100644 --- a/test/e2e/scenario/recorder/viewports.scenario.ts +++ b/test/e2e/scenario/recorder/viewports.scenario.ts @@ -7,6 +7,7 @@ import { test, expect } from '@playwright/test' import { wait } from '@datadog/browser-core/test/wait' import type { IntakeRegistry } from '../../lib/framework' import { createTest, bundleSetup, html } from '../../lib/framework' +import type { BrowserConfiguration } from '../../../browsers.conf' const NAVBAR_HEIGHT_CHANGE_UPPER_BOUND = 30 const VIEWPORT_META_TAGS = ` @@ -18,39 +19,37 @@ const VIEWPORT_META_TAGS = ` ` test.describe('recorder', () => { - test.beforeEach(({ hasTouch, browserName }, testInfo) => { - testInfo.skip(!hasTouch, 'no touch gesture support') + test.beforeEach(({ browserName }, testInfo) => { testInfo.skip(browserName !== 'chromium', 'only chromium supports touch gestures emulation for now (via CDP)') }) test.describe('layout viewport properties', () => { - test.describe('', () => { - createTest('getWindowWidth/Height should not be affected by pinch zoom') - .withRum() - .withSetup(bundleSetup) - .withBody(html`${VIEWPORT_META_TAGS}`) - .run(async ({ intakeRegistry, page, flushEvents }) => { - await buildScrollablePage(page) + createTest('getWindowWidth/Height should not be affected by pinch zoom') + .withRum() + .withSetup(bundleSetup) + .withBody(html`${VIEWPORT_META_TAGS}`) + .run(async ({ intakeRegistry, page, flushEvents }) => { + const { sessionName } = test.info().project.metadata as BrowserConfiguration + test.fixme(sessionName === 'Edge', 'In Edge, the ViewportResize record data is off by almost 20px') - const { innerWidth, innerHeight } = await getWindowInnerDimensions(page) + await buildScrollablePage(page) - await performSignificantZoom(page) + const { innerWidth, innerHeight } = await getWindowInnerDimensions(page) - await page.evaluate(() => { - window.dispatchEvent(new Event('resize')) - }) + await performSignificantZoom(page) - await flushEvents() - const lastViewportResizeData = getLastRecord(intakeRegistry, (segment) => - findAllIncrementalSnapshots(segment, IncrementalSource.ViewportResize) - ).data as ViewportResizeData + await page.evaluate(() => { + window.dispatchEvent(new Event('resize')) + }) - const scrollbarThicknessCorrection = getScrollbarThicknessCorrection(page) + await flushEvents() + const lastViewportResizeData = getLastRecord(intakeRegistry, (segment) => + findAllIncrementalSnapshots(segment, IncrementalSource.ViewportResize) + ).data as ViewportResizeData - expectToBeNearby(lastViewportResizeData.width, innerWidth - scrollbarThicknessCorrection) - expectToBeNearby(lastViewportResizeData.height, innerHeight - scrollbarThicknessCorrection) - }) - }) + expectToBeNearby(lastViewportResizeData.width, innerWidth) + expectToBeNearby(lastViewportResizeData.height, innerHeight) + }) /** * window.ScrollX/Y on some devices/browsers are changed by pinch zoom @@ -152,7 +151,7 @@ async function pinchZoom(page: Page, xChange: number) { { x: xBase + xOffsetFingerTwo, y: yBase, id: 1 }, ], }) - await wait(pauseDurationMs) + await page.waitForTimeout(pauseDurationMs) await cdp.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [ @@ -175,7 +174,7 @@ async function performSignificantZoom(page: Page) { await pinchZoom(page, 150) const nextVisualViewport = await getVisualViewport(page) // Test the test: ensure pinch zoom was applied - expect(initialVisualViewport.scale < nextVisualViewport.scale).toBeTruthy() + expect(nextVisualViewport.scale).toBeGreaterThan(initialVisualViewport.scale) } async function visualScrollVerticallyDown(page: Page, yChange: number) { @@ -249,40 +248,6 @@ function getWindowScroll(page: Page) { })) as Promise<{ scrollX: number; scrollY: number }> } -// TODO(playwright migration): I'm not sure if this is still needed? -// function getScrollbarThickness(page: Page): Promise { -// // https://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript#answer-13382873 -// return page.evaluate(() => { -// // Creating invisible container -// const outer = document.createElement('div') -// outer.style.visibility = 'hidden' -// outer.style.overflow = 'scroll' // forcing scrollbar to appear -// ;(outer.style as any).msOverflowStyle = 'scrollbar' // needed for WinJS apps -// document.body.appendChild(outer) -// // Creating inner element and placing it in the container -// const inner = document.createElement('div') -// outer.appendChild(inner) -// // Calculating difference between container's full width and the child width -// const scrollbarThickness = outer.offsetWidth - inner.offsetWidth -// // Removing temporary elements from the DOM -// document.body.removeChild(outer) -// return scrollbarThickness -// }) -// } - -// Mac OS X Chrome scrollbars are included here (~15px) which seems to be against spec -// Scrollbar edge-case handling not considered right now, further investigation needed -function getScrollbarThicknessCorrection(_page: Page): number { - const scrollbarThickness = 0 - - // TODO(playwright migration): I'm not sure if this is still needed? - // if (getBrowserName() === 'chrome' && getPlatformName() === 'macos') { - // scrollbarThickness = await getScrollbarThickness(page) - // } - - return scrollbarThickness -} - function getLastRecord(intakeRegistry: IntakeRegistry, filterMethod: (segment: any) => T[]): T { const segment = intakeRegistry.replaySegments.at(-1) const foundRecords = filterMethod(segment) diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index d74a5a7dfc..6c843adad5 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -1,6 +1,5 @@ import { RecordType } from '@datadog/browser-rum/src/types' import { test, expect } from '@playwright/test' -import { addTag } from '../../lib/helpers/tags' import { expireSession, findSessionCookie, renewSession } from '../../lib/helpers/session' import { bundleSetup, createTest, waitForRequests } from '../../lib/framework' From 3deaf8445d0e6fc60fabe4a14d6fdae8366fa516 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 11 Feb 2025 09:29:25 +0100 Subject: [PATCH 64/68] =?UTF-8?q?Revert=20"=E2=9C=85=20add=20Google=20pixe?= =?UTF-8?q?l=20pro=206=20tests=20on=20browserStack"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 77f9052cd460e81f7a252ba2db1e8a77da09f1af. --- .gitlab-ci.yml | 1 - test/e2e/browsers.conf.js | 13 +++++------ test/e2e/lib/framework/createTest.ts | 31 ++++++--------------------- test/e2e/lib/helpers/playwright.ts | 23 +++----------------- test/e2e/playwright.bs.config.ts | 32 ++++++++-------------------- 5 files changed, 25 insertions(+), 75 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 227c576150..9acad4d543 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -293,7 +293,6 @@ e2e-bs: - yarn - yarn build - yarn build:app - - yarn playwright install chromium-headless-shell --with-deps - FORCE_COLOR=1 ./scripts/test/ci-bs.sh test:e2e after_script: - node ./scripts/test/export-test-result.js e2e-bs diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index a6662f644a..affc011f3e 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -25,12 +25,13 @@ const browserConfigurations = [ os: 'OS X', osVersion: 'Big Sur', }, - { - sessionName: 'Chrome mobile', - name: 'chrome', - osVersion: '12.0', - device: 'Google Pixel 6 Pro', - }, + // { + // sessionName: 'Chrome mobile', + // name: 'chrome', + // os: 'android', + // osVersion: '12.0', + // device: 'Google Pixel 6 Pro', + // }, ] module.exports = { diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index 75aba91fae..501dd2bb8f 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,9 +1,8 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { DefaultPrivacyLevel } from '@datadog/browser-rum' -import type { AndroidDevice, BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' +import type { BrowserContext, Page, PlaywrightWorkerOptions } from '@playwright/test' import { test, expect } from '@playwright/test' -import { connectToAndroidDevice } from '../helpers/playwright' import type { Tag } from '../helpers/tags' import { addTag, addBrowserConfigurationTags } from '../helpers/tags' import { getRunId } from '../../../envUtils' @@ -191,11 +190,9 @@ function declareTestsForSetups( } function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFactory, runner: TestRunner) { - test(title, async ({ page, context, browserName, playwright }) => { - const projectMetadata = test.info().project.metadata as BrowserConfiguration - + test(title, async ({ page, context, browserName }) => { addTag('browserName' as any as Tag, browserName) - addBrowserConfigurationTags(projectMetadata) + addBrowserConfigurationTags(test.info().project.metadata as BrowserConfiguration) const title = test.info().titlePath.join(' > ') setupOptions.context.test_name = title @@ -203,18 +200,7 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa const servers = await getTestServers() const browserLogs = new BrowserLogsManager() - let _page = page - let _context = context - let _device: AndroidDevice | undefined - - if (projectMetadata.device) { - const { page, context, device } = await connectToAndroidDevice(playwright._android, projectMetadata) - _page = page - _context = context - _device = device - } - - const testContext = createTestContext(servers, _page, _context, browserLogs, browserName, setupOptions) + const testContext = createTestContext(servers, page, context, browserLogs, browserName, setupOptions) servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry)) const setup = factory(setupOptions, servers) @@ -227,7 +213,7 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa await runner(testContext) tearDownPassedTest(testContext) } finally { - await tearDownTest(testContext, _device) + await tearDownTest(testContext) } }) } @@ -293,15 +279,10 @@ function tearDownPassedTest({ intakeRegistry, withBrowserLogs }: TestContext) { }) } -async function tearDownTest({ flushEvents, deleteAllCookies, page }: TestContext, device?: AndroidDevice) { +async function tearDownTest({ flushEvents, deleteAllCookies }: TestContext) { await flushEvents() await deleteAllCookies() - if (device) { - await page.close() - await device.close() - } - const skipReason = test.info().annotations.find((annotation) => annotation.type === 'skip')?.description if (skipReason) { addTag('skip', skipReason) diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts index 4afa3854f3..8eba56854d 100644 --- a/test/e2e/lib/helpers/playwright.ts +++ b/test/e2e/lib/helpers/playwright.ts @@ -1,4 +1,4 @@ -import type { Android, PlaywrightWorkerOptions } from '@playwright/test' +import type { PlaywrightWorkerOptions } from '@playwright/test' import type { BrowserConfiguration } from '../../../browsers.conf' import { getBuildInfos } from '../../../envUtils' @@ -20,11 +20,12 @@ export function getEncodedCapabilities(configuration: BrowserConfiguration) { // see: https://www.browserstack.com/docs/automate/playwright/playwright-capabilities function getCapabilities(configuration: BrowserConfiguration) { - const capabilities: Record = { + return { os: configuration.os, os_version: configuration.osVersion, browser: configuration.name, browser_version: configuration.version, + ...(configuration.device ? { deviceName: configuration.device } : {}), 'browserstack.username': process.env.BS_USERNAME, 'browserstack.accessKey': process.env.BS_ACCESS_KEY, project: 'browser sdk e2e', @@ -38,22 +39,4 @@ function getCapabilities(configuration: BrowserConfiguration) { 'browserstack.networkLogs': false, 'browserstack.interactiveDebugging': false, } - - if (configuration.device) { - capabilities.deviceName = configuration.device - capabilities.realMobile = true - } - - return capabilities -} - -export async function connectToAndroidDevice(android: Android, configuration: BrowserConfiguration) { - const device = await android.connect( - `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}` - ) - await device.shell('am force-stop com.android.chrome') - const context = await device.launchBrowser() - const page = await context.newPage() - - return { page, context, device } } diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index a940798a43..72f5ca49a4 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -1,4 +1,3 @@ -import type { Project } from '@playwright/test' import { defineConfig } from '@playwright/test' import { getBrowserName, getEncodedCapabilities } from './lib/helpers/playwright' import { config as baseConfig } from './playwright.base.config' @@ -10,27 +9,14 @@ export default defineConfig({ workers: 5, // BrowserStack has a limit of 5 parallel sessions testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack // maxFailures: process.env.CI ? 1 : 0, - projects: browserConfigurations.map((configuration) => { - const project: Project = { - name: configuration.sessionName, - metadata: configuration, - } - - if (configuration.device) { - return { - ...project, - timeout: 60_000, - } - } - - return { - ...project, - use: { - browserName: getBrowserName(configuration.name), - connectOptions: { - wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, - }, + projects: browserConfigurations.map((configuration) => ({ + name: configuration.name, + metadata: configuration, + use: { + browserName: getBrowserName(configuration.name), + connectOptions: { + wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${getEncodedCapabilities(configuration)}`, }, - } - }), + }, + })), }) From 035937f7ec212ec0c1453a67b6c0ac061559e718 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 11 Feb 2025 09:31:49 +0100 Subject: [PATCH 65/68] =?UTF-8?q?=F0=9F=92=A5=20remove=20android=20support?= =?UTF-8?q?=20for=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/browsers.conf.js | 7 ------- test/e2e/lib/helpers/playwright.ts | 1 - test/e2e/playwright.bs.config.ts | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/test/e2e/browsers.conf.js b/test/e2e/browsers.conf.js index affc011f3e..39192da96e 100644 --- a/test/e2e/browsers.conf.js +++ b/test/e2e/browsers.conf.js @@ -25,13 +25,6 @@ const browserConfigurations = [ os: 'OS X', osVersion: 'Big Sur', }, - // { - // sessionName: 'Chrome mobile', - // name: 'chrome', - // os: 'android', - // osVersion: '12.0', - // device: 'Google Pixel 6 Pro', - // }, ] module.exports = { diff --git a/test/e2e/lib/helpers/playwright.ts b/test/e2e/lib/helpers/playwright.ts index 8eba56854d..b5b2862435 100644 --- a/test/e2e/lib/helpers/playwright.ts +++ b/test/e2e/lib/helpers/playwright.ts @@ -25,7 +25,6 @@ function getCapabilities(configuration: BrowserConfiguration) { os_version: configuration.osVersion, browser: configuration.name, browser_version: configuration.version, - ...(configuration.device ? { deviceName: configuration.device } : {}), 'browserstack.username': process.env.BS_USERNAME, 'browserstack.accessKey': process.env.BS_ACCESS_KEY, project: 'browser sdk e2e', diff --git a/test/e2e/playwright.bs.config.ts b/test/e2e/playwright.bs.config.ts index 72f5ca49a4..3cf2767e4f 100644 --- a/test/e2e/playwright.bs.config.ts +++ b/test/e2e/playwright.bs.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ testIgnore: ['**/developerExtension.scenario.ts', '**/s8sInject.scenario.ts'], // The following test won't run in the BrowserStack // maxFailures: process.env.CI ? 1 : 0, projects: browserConfigurations.map((configuration) => ({ - name: configuration.name, + name: configuration.sessionName, metadata: configuration, use: { browserName: getBrowserName(configuration.name), From 7e38ea315b36764a7ced45d5c1ef78f3ac1f8205 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 11 Feb 2025 10:15:27 +0100 Subject: [PATCH 66/68] =?UTF-8?q?=E2=9C=85=20review=20all=20skipped=20e2e?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/rum/actions.scenario.ts | 7 +------ test/e2e/scenario/rum/errors.scenario.ts | 14 ++++++-------- test/e2e/scenario/rum/resources.scenario.ts | 7 ++----- test/e2e/scenario/rum/views.scenario.ts | 8 ++------ test/e2e/scenario/transport.scenario.ts | 13 ++++--------- 5 files changed, 15 insertions(+), 34 deletions(-) diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 632d4d1d27..25debb8a57 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -97,12 +97,7 @@ test.describe('action collection', () => { }) `) - .run(async ({ intakeRegistry, flushEvents, browserName, page }) => { - test.skip( - browserName.includes('firefox'), - 'When the target element changes between mousedown and mouseup, Firefox does not dispatch a click event.' - ) - + .run(async ({ intakeRegistry, flushEvents, page }) => { const button = page.locator('button') await button.click() await flushEvents() diff --git a/test/e2e/scenario/rum/errors.scenario.ts b/test/e2e/scenario/rum/errors.scenario.ts index 0e7af431b9..521c02da67 100644 --- a/test/e2e/scenario/rum/errors.scenario.ts +++ b/test/e2e/scenario/rum/errors.scenario.ts @@ -122,10 +122,6 @@ test.describe('rum errors', () => { }) }) - // Ignore this test on Safari and firefox untill we upgrade because: - // - Safari < 15 don't report the property disposition - // - Firefox < 99 don't report csp violation at all - // TODO: Remove this condition when upgrading to Safari 15 and Firefox 99 (see: https://datadoghq.atlassian.net/browse/RUM-1063) createTest('send CSP violation errors') .withRum() .withBody( @@ -136,9 +132,6 @@ test.describe('rum errors', () => { `) ) .run(async ({ page, browserName, intakeRegistry, baseUrl, flushEvents, withBrowserLogs }) => { - const userAgent = await page.evaluate(() => navigator.userAgent) - test.skip(browserName.includes('firefox') || (browserName.includes('webkit') && userAgent.includes('Mac OS X'))) - const button = page.locator('button') await button.click() @@ -158,7 +151,12 @@ test.describe('rum errors', () => { }, }) withBrowserLogs((browserLogs) => { - expect(browserLogs).toHaveLength(1) + if (browserName === 'firefox') { + // Firefox has an additional Warning log: "Loading failed for the