Skip to content
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5dadb50
Add new Browser-rum-salesforce package
BeltranBulbarellaDD Jun 4, 2026
8c6cd15
Add support for Salesforce LWS
BeltranBulbarellaDD Jun 4, 2026
48f1282
Address comments from review
BeltranBulbarellaDD Jun 4, 2026
0fe7170
Address comments from Codex
BeltranBulbarellaDD Jun 4, 2026
ecbb855
Add unit tests
BeltranBulbarellaDD Jun 8, 2026
d9b95ba
Address location change observable comments
BeltranBulbarellaDD Jun 9, 2026
a55b33f
Make window optional in globalObject
BeltranBulbarellaDD Jun 9, 2026
3355032
Fix format and codex reviews
BeltranBulbarellaDD Jun 9, 2026
aa6f784
Fix linter
BeltranBulbarellaDD Jun 9, 2026
b260896
Initialize sf-lwc-app test app with eslint and prettier ignored
BeltranBulbarellaDD Jun 10, 2026
a742145
Add minimal LWC app for testing (Navigation, views, actions, resource…
BeltranBulbarellaDD Jun 10, 2026
14362c2
Merge branch 'main' into salesforce/add-e2e-tests_2
BeltranBulbarellaDD Jun 10, 2026
dfab355
fix: build, woke, home url in console output
BeltranBulbarellaDD Jun 10, 2026
93e8012
fix: rename sample json api url
BeltranBulbarellaDD Jun 10, 2026
158954d
format changes
BeltranBulbarellaDD Jun 10, 2026
b5b5beb
feat: build and deploy hashed bundles for salesforce
BeltranBulbarellaDD Jun 10, 2026
0424266
Add data test ids to salesforce app
BeltranBulbarellaDD Jun 11, 2026
bd55010
Add sf session helper
BeltranBulbarellaDD Jun 11, 2026
be88c73
Use playwright's route to intercept the requests
BeltranBulbarellaDD Jun 11, 2026
625e764
format and linter
BeltranBulbarellaDD Jun 11, 2026
1e47d83
Format SF html files. Add data-testid
BeltranBulbarellaDD Jun 11, 2026
e71a4dc
remove allowed tracing urls
BeltranBulbarellaDD Jun 11, 2026
203d830
Integrate withSalesforceApp and build into e2e tests
BeltranBulbarellaDD Jun 11, 2026
426f1c8
Print log when missing Salesforce credentials
BeltranBulbarellaDD Jun 12, 2026
7d00c84
use client credentials for Salesforce E2E tests
BeltranBulbarellaDD Jun 12, 2026
955154a
Use getSecretKey helper function for SF
BeltranBulbarellaDD Jun 12, 2026
2f36b8a
Rewrite structure
BeltranBulbarellaDD Jun 12, 2026
dd7c95f
Cleanup unused code and files
BeltranBulbarellaDD Jun 24, 2026
d32e6bf
Merge branch 'main' into salesforce/add-e2e-tests_2
BeltranBulbarellaDD Jun 24, 2026
9565382
Remove unused code
BeltranBulbarellaDD Jun 24, 2026
a5f06b4
Use query params for Datadog RUM config
BeltranBulbarellaDD Jun 24, 2026
7c535a0
Clean up Datadog RUM URL parameters, don't initialize RUM if not conf…
BeltranBulbarellaDD Jun 24, 2026
33581fd
chore: remove accidental rum-events-format gitlink
BeltranBulbarellaDD Jun 24, 2026
0560c00
Address codex comments
BeltranBulbarellaDD Jun 24, 2026
b37c157
Merge branch 'salesforce/add-e2e-tests_2' into beltran.bulbarella/sal…
BeltranBulbarellaDD Jun 25, 2026
3c6e244
Use CLI to authenticate and deploy Salesforce app
BeltranBulbarellaDD Jun 25, 2026
9c0f601
Use runSf
BeltranBulbarellaDD Jun 25, 2026
1f8d086
writeFileSync the bundle to the stable static resource
BeltranBulbarellaDD Jun 25, 2026
28889ce
Merge branch 'main' into beltran.bulbarella/salesforce/add-e2e-test-s…
BeltranBulbarellaDD Jun 29, 2026
bdf0ca2
Load local bundle into Salesforce static resource
BeltranBulbarellaDD Jun 29, 2026
2a4d1de
Address codex review
BeltranBulbarellaDD Jun 29, 2026
3e7e6a1
Extend withSalesforceApp
BeltranBulbarellaDD Jun 29, 2026
2e89087
Address codex reviews
BeltranBulbarellaDD Jun 29, 2026
bb0cc94
Use test RUM proxy config
BeltranBulbarellaDD Jun 30, 2026
0bbc090
Add comments, remove before send, add ignore error log
BeltranBulbarellaDD Jun 30, 2026
40131ed
Simply script, init
BeltranBulbarellaDD Jun 30, 2026
a98c30a
Simply error messagw
BeltranBulbarellaDD Jun 30, 2026
dc6cf91
Address codex, drop fallback from getSalesforceConfig()
BeltranBulbarellaDD Jun 30, 2026
8d16cc6
Simplify scripts and test setup
BeltranBulbarellaDD Jul 1, 2026
6fb1287
fix head error
BeltranBulbarellaDD Jul 1, 2026
85c69bc
Separate authentication from test setup
BeltranBulbarellaDD Jul 1, 2026
f3942ac
Skip test on non-chromium browsers
BeltranBulbarellaDD Jul 2, 2026
2e3b0e9
Improve README for Salesforce app
BeltranBulbarellaDD Jul 2, 2026
bbd5237
skip test on non-chromium browsers
BeltranBulbarellaDD Jul 2, 2026
de90e80
Address codex review
BeltranBulbarellaDD Jul 2, 2026
6e02888
Check credentials before building Salesforce URL
BeltranBulbarellaDD Jul 2, 2026
051276e
Authenticate on every deploy, use sf CLI JSON output
BeltranBulbarellaDD Jul 2, 2026
2a53ea0
Authenticate on every salesforce script call
BeltranBulbarellaDD Jul 2, 2026
96c2e6e
Simplify Salesforce app RUM configuration
BeltranBulbarellaDD Jul 2, 2026
c602507
Simplify config, address comments
BeltranBulbarellaDD Jul 2, 2026
693e87d
Add more tests, simplify test app config
BeltranBulbarellaDD Jul 2, 2026
2d97301
Add default init configuration to SF test app.
BeltranBulbarellaDD Jul 2, 2026
5e23daa
Address comments
BeltranBulbarellaDD Jul 3, 2026
9dc1458
Fix deploy script to use --env-file-if-exists=.env
BeltranBulbarellaDD Jul 3, 2026
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# BROWSERSTACK CREDENTIALS
BS_USERNAME=xxx
BS_ACCESS_KEY=xxx

# Salesforce credentials
SF_LWC_CLIENT_ID=xxx
SF_LWC_USERNAME=xxx
SF_LWC_INSTANCE_URL=xxx
SF_LWC_JWT_PRIVATE_KEY_B64=xxx
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"build": "yarn workspaces foreach --all --parallel --topological-dev run build",
"build:bundle": "yarn workspaces foreach --all --parallel run build:bundle",
"build:apps": "node scripts/build/build-test-apps.ts",
"salesforce:deploy-app": "yarn workspace @datadog/browser-rum-slim build:bundle && node -env-file-if-exists=.env scripts/salesforce-lwc-app.ts deploy-app",
Comment thread
BeltranBulbarellaDD marked this conversation as resolved.
Outdated
"salesforce:get-url": "node scripts/salesforce-lwc-app.ts get-url",
"build:docs:json": "typedoc --logLevel Verbose --json ./generated-docs.json",
"build:docs:html": "typedoc --out ./generated-docs",
"serve:docs": "yarn build:docs:html && npx http-server ./generated-docs -p 8080 -o",
Expand Down
9 changes: 9 additions & 0 deletions scripts/build/build-test-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const APPS: AppConfig[] = [
{ name: 'vue-router-app' },
{ name: 'nuxt-app' },
{ name: 'instrumentation-overhead' },
{ name: 'sf-lwc-app', builderFn: buildSalesforceLwcApp },

// React Router apps
{ name: 'react-router-app' },
Expand Down Expand Up @@ -157,6 +158,14 @@ async function buildApp(appName: string) {
await command`yarn build`.withCurrentWorkingDirectory(appPath).runAsync()
}

function buildSalesforceLwcApp() {
const sourceBundle = 'packages/browser-rum-slim/bundle/datadog-rum-slim.js'
const targetBundle = 'test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.js'

printLog('Building app at test/apps/sf-lwc-app...')
fs.copyFileSync(sourceBundle, targetBundle)
}

async function buildReactRouterV6App() {
await buildGeneratedApp('react-router-app', 'react-router-v6-app', async (appPath) => {
await modifyFile(path.join(appPath, 'package.json'), (content: string) =>
Expand Down
16 changes: 16 additions & 0 deletions scripts/lib/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ export function getBrowserStackAccessKey(): string {
return getSecretKey('ci.browser-sdk.bs_access_key')
}

export function getSfLwcClientId(): string {
return process.env.SF_LWC_CLIENT_ID ?? getSecretKey('ci.browser-sdk.sf_lwc_client_id')
}

export function getSfLwcUsername(): string {
return process.env.SF_LWC_USERNAME ?? getSecretKey('ci.browser-sdk.sf_lwc_username')
}

export function getSfLwcInstanceUrl(): string {
return process.env.SF_LWC_INSTANCE_URL ?? getSecretKey('ci.browser-sdk.sf_lwc_instance_url')
}

export function getSfLwcJwtPrivateKey(): string {
return process.env.SF_LWC_JWT_PRIVATE_KEY_B64 ?? getSecretKey('ci.browser-sdk.sf_lwc_jwt_private_key_b64')
}

function getSecretKey(name: string): string {
return command`
aws ssm get-parameter --region=us-east-1 --with-decryption --query=Parameter.Value --out=text --name=${name}
Expand Down
98 changes: 98 additions & 0 deletions scripts/salesforce-lwc-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { tmpdir } from 'node:os'
import { parseArgs } from 'node:util'

import { printLog, runMain } from './lib/executionUtils.ts'
import { getSfLwcClientId, getSfLwcInstanceUrl, getSfLwcJwtPrivateKey, getSfLwcUsername } from './lib/secrets.ts'
import { command } from './lib/command.ts'

const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const salesforceAppDir = resolve(repositoryRoot, 'test/apps/sf-lwc-app')
const SALESFORCE_HOME_PATH = '/lightning/app/c__SF_LWC_App/page/home'
const TARGET_ORG = 'sf-lwc-ci'

runMain(() => {
const { values, positionals } = parseArgs({
allowPositionals: true,
options: {
help: {
type: 'boolean',
short: 'h',
},
},
})

if (values.help) {
showUsageAndExit()
}

if (positionals.length !== 1) {
throw new Error('Usage: node scripts/salesforce-lwc-app.ts <deploy-app|get-url>')
}

const commandName = positionals[0]

switch (commandName) {
// Deploy the app to the Salesforce org. To be done only when the app is updated.
case 'deploy-app':
deployApp()
break
// Get the authenticated URL of the app.
case 'get-url':
printSalesforceLwcUrl()
break
default:
throw new Error(`Unknown command "${commandName ?? ''}". Expected: deploy-app|get-url`)
}
})

function showUsageAndExit() {
console.log('Usage: node scripts/salesforce-lwc-app.ts <deploy-app|get-url>')
process.exit(0)
}

function authenticate(targetOrg: string) {
// Temporary directory holding the JWT private key for the duration of authentication.
// Using a unique temp dir avoids collisions when multiple CI jobs run in parallel.
const keyDirectory = mkdtempSync(resolve(tmpdir(), 'sf-lwc-jwt-'))
const serverKeyPath = resolve(keyDirectory, 'server.key')

try {
writeFileSync(serverKeyPath, Buffer.from(getSfLwcJwtPrivateKey(), 'base64').toString('utf8'), { mode: 0o600 })
// writeFileSync mode can be masked by the process umask; chmodSync guarantees owner-only access.
chmodSync(serverKeyPath, 0o600)

printLog(`Authenticating Salesforce CLI alias ${targetOrg}...`)
command`sf org login jwt --client-id ${getSfLwcClientId()} --jwt-key-file ${serverKeyPath} --username ${getSfLwcUsername()} --instance-url ${getSfLwcInstanceUrl()} --alias ${targetOrg}`
.withCurrentWorkingDirectory(salesforceAppDir)
.withLogs()
.run()
printLog(`Salesforce CLI authenticated as ${targetOrg}.`)
} finally {
rmSync(keyDirectory, { recursive: true, force: true })
}
}

function deployApp() {
authenticate(TARGET_ORG)

printLog(`Deploying Salesforce LWC app to ${TARGET_ORG}...`)
command`sf project deploy start --target-org ${TARGET_ORG} --source-dir force-app --ignore-conflicts --concise`
.withCurrentWorkingDirectory(salesforceAppDir)
.withLogs()
.run()
printLog('Salesforce LWC app deployed.')
}

function printSalesforceLwcUrl(): void {
const path = new URL(SALESFORCE_HOME_PATH, 'https://salesforce.local')

authenticate(TARGET_ORG)

command`sf org open --target-org ${TARGET_ORG} --path ${path.pathname}${path.search} --url-only`
.withCurrentWorkingDirectory(salesforceAppDir)
.withLogs()
.run()
}
2 changes: 1 addition & 1 deletion test/apps/sf-lwc-app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.sf
.sfdx
/force-app/main/default/staticresources/datadog_rum_slim.js
/force-app/main/default/staticresources/datadog_rum_slim.js
55 changes: 50 additions & 5 deletions test/apps/sf-lwc-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,59 @@ This app is Lightning-only.
- A `Product Explorer` app page with three hardcoded editable products
- `c:datadogInit` in the utility bar, backed by the `datadog_rum_slim` static resource

## Deploy
## Authentication

From this directory:
The Salesforce flow uses the Salesforce CLI with a JWT keypair. There is no separate manual auth step: `yarn salesforce:deploy-app` and `yarn salesforce:get-url` always (re-)authenticate the `sf-lwc-ci` alias before running, since the JWT private key file used for authentication is deleted right after login and can't be reused to refresh a cached session.

Credentials are set as CI variables.

For local overrides, set the matching environment variables from `.env.example`:

## Initial App Deploy

This app runs from Salesforce metadata already deployed to
the Salesforce org, so any change to that metadata (Apex, LWC markup/config, permission sets, etc.) requires a full
redeploy to take effect.

For E2E testing, deployment is not necesary since we will override the deployed rum_slim bundle with Playwright.

```sh
yarn salesforce:deploy-app
Comment thread
BeltranBulbarellaDD marked this conversation as resolved.
```

This builds the local RUM slim bundle, copies it to the stable `datadog_rum_slim` static resource, and deploys the app metadata.

## Local Bundle

Regular test runs do not deploy the current SDK bundle to Salesforce.
Build the test apps from the repository root instead:

```sh
yarn build:apps --app sf-lwc-app
```

This copies the locally built RUM slim bundle into the ignored stable `datadog_rum_slim` static resource file.
Playwright fulfills Salesforce static resource requests with this local file during E2E tests.

## Open The App

Print an authenticated URL for the app:

```sh
yarn run setup -o engrumdev --ignore-conflicts
yarn salesforce:get-url
```

The setup script copies the local RUM slim bundle into the static resource, deploys the app, assigns the app permission set to the target user, and prints the app-specific Home URL.
The printed URL is authenticated and should be treated as sensitive.
E2E tests don't use this script: they build their own authenticated URL via the JWT/REST flow in
`test/e2e/lib/framework/buildSalesforceLwcUrl.ts`, and inject the RUM configuration on the page as
`window.RUM_CONFIGURATION`.

## Run E2E Tests

If prompted for a user. Log into 1Password and use `beltran.bulbarella@datadoghq.com.engrumdev`
Build the SDK and test apps, then run the Salesforce scenario:

```sh
yarn build
yarn build:apps --app sf-lwc-app
yarn test:e2e --project=chromium --grep salesforce
```
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class CustomActionButtons extends LightningElement {
}

getResourceTestUrl(token) {
return `https://sample-json-api.com/products/1?${token}`
return `${window.location.origin}/services/data/?${token}`
}

getComposedPathNames(event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,10 @@ import { loadScript } from 'lightning/platformResourceLoader'
let datadogInitialization
let lastStartedUrl

const DATADOG_PARAMS = ['c__applicationId', 'c__clientToken', 'c__env', 'c__service', 'c__site']

const cleanUrl = (url) => {
const cleanUrl = new URL(url, window.location.origin)
DATADOG_PARAMS.forEach((param) => cleanUrl.searchParams.delete(param))
return cleanUrl.href
}

const defaultDatadogRumConfig = {
trackViewsManually: true,
trackEarlyRequests: true,
trackLongTasks: true,
trackResources: true,
trackUserInteractions: true,
beforeSend: (event) => {
if (event.view) {
const sanitizedViewUrl = new URL(cleanUrl(event.view.url))
event.view.url = sanitizedViewUrl.href
event.view.name = sanitizedViewUrl.pathname + sanitizedViewUrl.search + sanitizedViewUrl.hash
}
if (event.resource?.url) {
event.resource.url = cleanUrl(event.resource.url)
}
},
const defaultInitConfiguration = {
applicationId: 'xxx',
clientToken: 'xxx',
site: 'datadoghq.com',
}

export default class DatadogInit extends NavigationMixin(LightningElement) {
Expand Down Expand Up @@ -70,23 +50,8 @@ export default class DatadogInit extends NavigationMixin(LightningElement) {

loadDatadogRum() {
return loadScript(this, datadogRumSlim).then(() => {
const searchParams = new URLSearchParams(window.location.search)
const applicationId = searchParams.get('c__applicationId')
const clientToken = searchParams.get('c__clientToken')

if (!applicationId || !clientToken) {
window.console.warn('Datadog RUM not initialized: missing c__applicationId or c__clientToken')
return
}

window.DD_RUM.init({
applicationId,
clientToken,
env: searchParams.get('c__env'),
service: searchParams.get('c__service'),
site: searchParams.get('c__site'),
...defaultDatadogRumConfig,
})
window.DD_RUM.setGlobalContext(window.RUM_CONTEXT)
window.DD_RUM.init({ ...defaultInitConfiguration, ...window.RUM_CONFIGURATION })
lastStartedUrl = window.location.pathname + window.location.search + window.location.hash
window.DD_RUM.startView({
name: lastStartedUrl,
Expand Down
5 changes: 1 addition & 4 deletions test/apps/sf-lwc-app/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"name": "sf-lwc-app",
"private": true,
"description": "Salesforce Lightning app for browser-sdk Salesforce testing",
"scripts": {
"setup": "node scripts/setup.mjs"
}
"description": "Salesforce Lightning app for browser-sdk Salesforce testing"
}
Loading
Loading