Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 26 additions & 39 deletions test/e2e/lib/framework/createTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,14 @@ import { IntakeRegistry } from './intakeRegistry'
import { flushEvents } from './flushEvents'
import type { Servers } from './httpServers'
import { getTestServers, waitForServersIdle } from './httpServers'
import type { SetupFactory, SetupOptions } from './pageSetups'
import { html, DEFAULT_SETUPS, npmSetup, reactSetup } from './pageSetups'
import type { SetupFactory, SetupOptions, WorkerImplementationFactory } from './pageSetups'
import { workerSetup, html, DEFAULT_SETUPS, npmSetup, reactSetup } from './pageSetups'
import { createIntakeServerApp } from './serverApps/intake'
import { createMockServerApp } from './serverApps/mock'
import type { Extension } from './createExtension'
import { isBrowserStack } from './environment'

interface LogsWorkerOptions {
importScript?: boolean
nativeLog?: boolean
}
import '../types/global'

export function createTest(title: string) {
return new TestBuilder(title)
Expand Down Expand Up @@ -62,8 +59,8 @@ class TestBuilder {
rumConfiguration?: RumInitConfiguration
logsConfiguration?: LogsInitConfiguration
} = {}
private useServiceWorker: boolean = false
private hostName?: string
private workerImplementationFactory?: WorkerImplementationFactory

constructor(private title: string) {}

Expand Down Expand Up @@ -135,37 +132,21 @@ class TestBuilder {
return this
}

withWorker({ importScript = false, nativeLog = false }: LogsWorkerOptions = {}) {
if (!this.useServiceWorker) {
this.useServiceWorker = true

const isModule = !importScript

const params = []
if (importScript) {
params.push('importScripts=true')
}
if (nativeLog) {
params.push('nativeLog=true')
}

const query = params.length > 0 ? `?${params.join('&')}` : ''
const url = `/sw.js${query}`

const options = isModule ? '{ type: "module" }' : '{}'

// Service workers require HTTPS or localhost due to browser security restrictions
this.hostName = 'localhost'
this.withBody(html`
<script>
if (!window.myServiceWorker && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('${url}', ${options}).then((registration) => {
window.myServiceWorker = registration
})
}
</script>
`)
}
withWorker(implementation: WorkerImplementationFactory, options: RegistrationOptions = {}) {
implementation.isModule = options.type === 'module'
this.workerImplementationFactory = implementation

// Service workers require HTTPS or localhost due to browser security restrictions
this.withHostName('localhost')
this.withBody(html`
<script>
if (!window.myServiceWorker && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', ${JSON.stringify(options)}).then((registration) => {
window.myServiceWorker = registration
})
}
</script>
`)

return this
}
Expand Down Expand Up @@ -194,6 +175,7 @@ class TestBuilder {
testFixture: this.testFixture,
extension: this.extension,
hostName: this.hostName,
workerImplementation: this.workerImplementationFactory,
}

if (this.alsoRunWithRumSlim) {
Expand Down Expand Up @@ -270,7 +252,12 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa
servers.intake.bindServerApp(createIntakeServerApp(testContext.intakeRegistry))

const setup = factory(setupOptions, servers)
servers.base.bindServerApp(createMockServerApp(servers, setup, setupOptions.remoteConfiguration))
servers.base.bindServerApp(
createMockServerApp(servers, setup, {
remoteConfiguration: setupOptions.remoteConfiguration,
workerImplementation: setupOptions.workerImplementation && workerSetup(setupOptions, servers),
})
)
servers.crossOrigin.bindServerApp(createMockServerApp(servers, setup))

await setUpTest(browserLogs, testContext)
Expand Down
10 changes: 9 additions & 1 deletion test/e2e/lib/framework/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
export { createTest } from './createTest'
export { DEFAULT_RUM_CONFIGURATION, DEFAULT_LOGS_CONFIGURATION } from '../helpers/configuration'
export { createExtension } from './createExtension'
export { bundleSetup, html, npmSetup, reactSetup, formatConfiguration, createCrossOriginScriptUrls } from './pageSetups'
export {
bundleSetup,
html,
js,
npmSetup,
reactSetup,
formatConfiguration,
createCrossOriginScriptUrls,
} from './pageSetups'
export { IntakeRegistry } from './intakeRegistry'
export { getTestServers, waitForServersIdle } from './httpServers'
export { flushEvents } from './flushEvents'
Expand Down
37 changes: 24 additions & 13 deletions test/e2e/lib/framework/pageSetups.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { generateUUID, INTAKE_URL_PARAMETERS } from '@datadog/browser-core'
import type { LogsInitConfiguration } from '@datadog/browser-logs'
import type { LogsInitConfiguration, DatadogLogs } from '@datadog/browser-logs'
import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core'
import type { DatadogRum } from '@datadog/browser-rum'
import type test from '@playwright/test'
import { DEFAULT_LOGS_CONFIGURATION } from '../helpers/configuration'
import { isBrowserStack, isContinuousIntegration } from './environment'
import type { Servers } from './httpServers'

export interface WorkerImplementationFactory {
(self: WorkerGlobalScope & { DD_LOGS?: DatadogLogs; DD_RUM?: DatadogRum }): void
isModule?: boolean
}
export interface SetupOptions {
rum?: RumInitConfiguration
useRumSlim: boolean
Expand All @@ -27,6 +32,7 @@ export interface SetupOptions {
logsConfiguration?: LogsInitConfiguration
}
hostName?: string
workerImplementation?: WorkerImplementationFactory
}

export interface WorkerOptions {
Expand Down Expand Up @@ -209,20 +215,25 @@ export function reactSetup(options: SetupOptions, servers: Servers, appName: str
})
}

export function workerSetup(options: WorkerOptions, servers: Servers) {
return js`
${options.importScripts ? js`importScripts('/datadog-logs.js');` : js`import '/datadog-logs.js';`}
export function workerSetup(options: SetupOptions, servers: Servers) {
let script = ''

if (!options.workerImplementation) {
return script
}

if (options.logs) {
script += js`
${options.workerImplementation.isModule ? js`import '/datadog-logs.js';` : js`importScripts('/datadog-logs.js');`}

// Initialize DD_LOGS in service worker
DD_LOGS.init(${formatConfiguration({ ...DEFAULT_LOGS_CONFIGURATION, forwardConsoleLogs: 'all', forwardErrorsToLogs: true }, servers)})

// Handle messages from main thread
self.addEventListener('message', (event) => {
const message = event.data;

${options.nativeLog ? js`console.log(message);` : js`DD_LOGS.logger.log(message);`}
});
DD_LOGS.init(${formatConfiguration({ ...DEFAULT_LOGS_CONFIGURATION, ...options.logs }, servers)})
`
}

script += `;(${options.workerImplementation.toString()})(self);`

return script
}

export function basePage({ header, body }: { header?: string; body?: string }) {
Expand All @@ -244,7 +255,7 @@ export function html(parts: readonly string[], ...vars: string[]) {
return parts.reduce((full, part, index) => full + vars[index - 1] + part)
}

function js(parts: readonly string[], ...vars: string[]) {
export function js(parts: readonly string[], ...vars: string[]) {
return parts.reduce((full, part, index) => full + vars[index - 1] + part)
}

Expand Down
13 changes: 6 additions & 7 deletions test/e2e/lib/framework/serverApps/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import type { RemoteConfiguration } from '@datadog/browser-rum-core'
import { getSdkBundlePath, getTestAppBundlePath } from '../sdkBuilds'
import type { MockServerApp, Servers } from '../httpServers'
import { DEV_SERVER_BASE_URL } from '../../helpers/playwright'
import { workerSetup } from '../pageSetups'

export const LARGE_RESPONSE_MIN_BYTE_SIZE = 100_000

interface MockServerOptions {
remoteConfiguration?: RemoteConfiguration
workerImplementation?: string
}
export function createMockServerApp(
servers: Servers,
setup: string,
remoteConfiguration?: RemoteConfiguration
{ remoteConfiguration, workerImplementation }: MockServerOptions = {}
): MockServerApp {
const app = express()
let largeResponseBytesWritten = 0
Expand Down Expand Up @@ -46,11 +49,7 @@ export function createMockServerApp(
})

app.get('/sw.js', (_req, res) => {
const query = _req.query

res
.contentType('application/javascript')
.send(workerSetup({ importScripts: Boolean(query.importScripts), nativeLog: Boolean(query.nativeLog) }, servers))
res.contentType('application/javascript').send(workerImplementation)
})

function generateLargeResponse(res: ServerResponse, chunkText: string) {
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/lib/types/global.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { LogsGlobal } from '@datadog/browser-logs'
import type { RumGlobal } from '@datadog/browser-rum'
import type { DatadogLogs } from '@datadog/browser-logs'
import type { DatadogRum } from '@datadog/browser-rum'

declare global {
interface Window {
DD_LOGS?: LogsGlobal
DD_RUM?: RumGlobal
DD_LOGS?: DatadogLogs
DD_RUM?: DatadogRum
}
}
30 changes: 27 additions & 3 deletions test/e2e/scenario/logs.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ declare global {

test.describe('logs', () => {
createTest('service worker with worker logs - esm')
.withWorker()
.withLogs()
.withWorker(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to move this to the createTest? So that we do something like:
.withWorker({ isModule: true, handleMessageWith: 'logger' })
Similar to what we do in:

.withExtension(createExtension(path.join(BASE_PATH, name)).withRum().withLogs())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is to avoid that scenario and be more generic, or that's what I understood from @bcaudan feedback.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could make the API more convenient by:

  • providing a function to withWorker, to work directly with code instead of a block of text
  • have the worker implementation as complete as possible:
    • let it import the script rather than handle that in workerSetup, it would be easier to follow
    • provide it the consolidated configuration

to be able to do something like:

.withWorker((config) => {
  import '/datadog-logs.js'
      
  // Initialize DD_LOGS in service worker
  DD_LOGS.init(config)

  // Handle messages from main thread
  self.addEventListener('message', (event) => {
    const message = event.data
    DD_LOGS.logger.log(message)
  })
})

Copy link
Contributor Author

@mormubis mormubis Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcaudan I had this as an earlier version, but then I found that the current implementation is closer to what we do with Logs/RUM (withLogs and withRUM).

function (self) {
self.addEventListener('message', (event) => {
const message = (event as MessageEvent<string>).data

self.DD_LOGS!.logger.log(message)
})
},
{ type: 'module' }
)
.run(async ({ flushEvents, intakeRegistry, browserName, interactWithWorker }) => {
test.skip(browserName !== 'chromium', 'Non-Chromium browsers do not support ES modules in Service Workers')

Expand All @@ -28,7 +38,14 @@ test.describe('logs', () => {
})

createTest('service worker with worker logs - importScripts')
.withWorker({ importScript: true })
.withLogs()
.withWorker(function (self) {
self.addEventListener('message', (event) => {
const message = (event as MessageEvent<string>).data

self.DD_LOGS!.logger.log(message)
})
})
.run(async ({ flushEvents, intakeRegistry, browserName, interactWithWorker }) => {
test.skip(
browserName === 'webkit',
Expand All @@ -46,7 +63,14 @@ test.describe('logs', () => {
})

createTest('service worker console forwarding')
.withWorker({ importScript: true, nativeLog: true })
.withLogs({ forwardConsoleLogs: 'all', forwardErrorsToLogs: true })
.withWorker(function (self) {
self.addEventListener('message', (event) => {
const message = (event as MessageEvent<string>).data

console.log(message)
})
})
.run(async ({ flushEvents, intakeRegistry, interactWithWorker, browserName }) => {
test.skip(
browserName === 'webkit',
Expand Down
4 changes: 3 additions & 1 deletion test/e2e/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"target": "ES2022",
"module": "ES2020",
"types": ["node", "ajv"],
"lib": ["ESNext", "DOM", "WebWorker"],
"allowJs": true,
"noEmit": true
}
},
"include": ["."]
}