Skip to content

Commit bdf0ca2

Browse files
Load local bundle into Salesforce static resource
1 parent 28889ce commit bdf0ca2

9 files changed

Lines changed: 77 additions & 100 deletions

File tree

.gitlab-ci.yml

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -254,29 +254,13 @@ e2e:
254254
- yarn build:apps
255255
# Browsers are pre-installed in the CI image. If playwright (current or pinned 1.40.1)
256256
# is upgraded without rebuilding the image, this job will crash — rebuild the image to fix it.
257-
- FORCE_COLOR=1 PW_BROWSER=$BROWSER yarn test:e2e --project=$BROWSER --grep-invert @salesforce
258-
after_script:
259-
- node ./scripts/test/export-test-result.ts e2e
260-
261-
salesforce-e2e:
262-
extends:
263-
- .base-configuration
264-
- .test-allowed-branches
265-
- .resource-allocation-4-cpus
266-
resource_group: salesforce-engrumdev
267-
interruptible: true
268-
artifacts:
269-
when: always
270-
reports:
271-
junit: test-report/e2e/*.xml
272-
script:
273-
- yarn
274-
- yarn build
275-
- yarn build:apps
276-
- node scripts/salesforce-lwc-app.ts auth
277-
- export SF_TARGET_ORG=sf-lwc-ci
278-
- yarn salesforce:deploy-bundle
279-
- FORCE_COLOR=1 PW_BROWSER=chromium yarn test:e2e --project=chromium --grep @salesforce
257+
- |
258+
if [ "$BROWSER" = "chromium" ]; then
259+
node scripts/salesforce-lwc-app.ts auth
260+
FORCE_COLOR=1 PW_BROWSER=$BROWSER yarn test:e2e --project=$BROWSER
261+
else
262+
FORCE_COLOR=1 PW_BROWSER=$BROWSER yarn test:e2e --project=$BROWSER --grep-invert @salesforce
263+
fi
280264
after_script:
281265
- node ./scripts/test/export-test-result.ts e2e
282266

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
"build:bundle": "yarn workspaces foreach --all --parallel run build:bundle",
1717
"build:apps": "node scripts/build/build-test-apps.ts",
1818
"salesforce:deploy-app": "yarn workspace @datadog/browser-rum-slim build:bundle && node scripts/salesforce-lwc-app.ts deploy-app",
19-
"salesforce:deploy-bundle": "yarn workspace @datadog/browser-rum-slim build:bundle && node scripts/salesforce-lwc-app.ts deploy-bundle",
2019
"salesforce:open": "node scripts/salesforce-lwc-app.ts open-url",
2120
"build:docs:json": "typedoc --logLevel Verbose --json ./generated-docs.json",
2221
"build:docs:html": "typedoc --out ./generated-docs",

scripts/build/build-test-apps.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const APPS: AppConfig[] = [
3535
{ name: 'vue-router-app' },
3636
{ name: 'nuxt-app' },
3737
{ name: 'instrumentation-overhead' },
38+
{ name: 'sf-lwc-app', builderFn: buildSalesforceLwcApp },
3839

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

161+
function buildSalesforceLwcApp() {
162+
const sourceBundle = 'packages/browser-rum-slim/bundle/datadog-rum-slim.js'
163+
const targetBundle = 'test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.js'
164+
165+
printLog('Building app at test/apps/sf-lwc-app...')
166+
fs.copyFileSync(sourceBundle, targetBundle)
167+
}
168+
160169
async function buildReactRouterV6App() {
161170
await buildGeneratedApp('react-router-app', 'react-router-v6-app', async (appPath) => {
162171
await modifyFile(path.join(appPath, 'package.json'), (content: string) =>

scripts/salesforce-lwc-app.ts

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
2-
import { createHash } from 'node:crypto'
1+
import { chmodSync, copyFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
32
import { dirname, resolve } from 'node:path'
43
import { spawnSync } from 'node:child_process'
54
import { fileURLToPath } from 'node:url'
@@ -13,9 +12,6 @@ const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
1312
const salesforceAppDir = resolve(repositoryRoot, 'test/apps/sf-lwc-app')
1413
const bundlePath = resolve(repositoryRoot, 'packages/browser-rum-slim/bundle/datadog-rum-slim.js')
1514
const stableStaticResourcePath = resolve(salesforceAppDir, 'force-app/main/default/staticresources/datadog_rum_slim.js')
16-
const generatedStateDir = resolve(salesforceAppDir, '.sf-e2e')
17-
const generatedStaticResourcesDir = resolve(generatedStateDir, 'force-app/main/default/staticresources')
18-
const resourceNamePath = resolve(generatedStateDir, 'resource-name')
1915
const salesforceHomePath = '/lightning/app/c__SF_LWC_App/page/home'
2016
const defaultTargetOrg = 'sf-lwc-ci'
2117
const salesforceCliPackage = '@salesforce/cli@2.139.6'
@@ -30,14 +26,11 @@ runMain(() => {
3026
case 'deploy-app':
3127
deployApp()
3228
break
33-
case 'deploy-bundle':
34-
deployBundle()
35-
break
3629
case 'open-url':
3730
process.stdout.write(`${buildOpenUrl()}\n`)
3831
break
3932
default:
40-
throw new Error('Usage: node scripts/salesforce-lwc-app.ts <auth|deploy-app|deploy-bundle|open-url>')
33+
throw new Error('Usage: node scripts/salesforce-lwc-app.ts <auth|deploy-app|open-url>')
4134
}
4235
})
4336

@@ -77,32 +70,45 @@ function deployApp() {
7770

7871
printLog(`Deploying Salesforce LWC app to ${targetOrg}...`)
7972
copyFileSync(bundlePath, stableStaticResourcePath)
80-
runSf(['project', 'deploy', 'start', '--target-org', targetOrg, '--source-dir', 'force-app'], { stdio: 'inherit' })
8173

82-
printLog('Salesforce LWC app deployed.')
83-
}
74+
const deployArgs = [
75+
'project',
76+
'deploy',
77+
'start',
78+
'--target-org',
79+
targetOrg,
80+
'--source-dir',
81+
'force-app',
82+
'--ignore-conflicts',
83+
]
84+
const spawnOptions: SpawnSyncOptionsWithStringEncoding = { encoding: 'utf8', cwd: salesforceAppDir, stdio: 'pipe' }
8485

85-
function deployBundle() {
86-
const targetOrg = getTargetOrg()
87-
const bundle = readFileSync(bundlePath)
88-
const resourceName = computeResourceName(bundle)
89-
90-
printLog(`Deploying Salesforce LWC bundle ${resourceName} to ${targetOrg}...`)
91-
writeFileSync(stableStaticResourcePath, bundle)
92-
writeGeneratedStaticResource(resourceName, bundle)
93-
runSf(['project', 'deploy', 'start', '--target-org', targetOrg, '--source-dir', generatedStaticResourcesDir], {
94-
stdio: 'inherit',
95-
})
96-
writeFileSync(resourceNamePath, `${resourceName}\n`)
97-
printLog(`Salesforce LWC bundle deployed: ${resourceName}`)
86+
let result = spawnSync('sf', deployArgs, spawnOptions)
87+
if (result.error && (result.error as NodeJS.ErrnoException).code === 'ENOENT') {
88+
result = spawnSync('yarn', ['dlx', '-p', salesforceCliPackage, 'sf', ...deployArgs], spawnOptions)
89+
}
90+
91+
if (result.stdout) {
92+
process.stdout.write(result.stdout)
93+
}
94+
95+
if (result.status !== 0) {
96+
const output = (result.stdout ?? '') + (result.stderr ?? '')
97+
// Source tracking fails on orgs that don't support it (e.g. developer orgs), but components
98+
// are deployed successfully. Treat this specific post-deploy tracking error as non-fatal.
99+
if (!output.includes('Could not find HEAD')) {
100+
throw new Error(result.stderr || result.stdout || result.error?.message)
101+
}
102+
printLog('Warning: Source tracking update failed (Could not find HEAD). Components were deployed successfully.')
103+
}
104+
105+
printLog('Salesforce LWC app deployed.')
98106
}
99107

100108
function buildOpenUrl(): string {
101109
const targetOrg = getTargetOrg()
102-
const resourceName = readResourceName()
103110
const path = new URL(salesforceHomePath, 'https://salesforce.local')
104111

105-
path.searchParams.set('c__datadogResourceName', resourceName)
106112
path.searchParams.set(
107113
'c__datadogInitConfiguration',
108114
JSON.stringify({
@@ -195,31 +201,3 @@ function stripCliControlCharacters(output: string): string {
195201
function getTargetOrg(): string {
196202
return process.env.SF_TARGET_ORG || defaultTargetOrg
197203
}
198-
199-
function computeResourceName(bundle: Buffer): string {
200-
const hash = createHash('sha256').update(bundle).digest('hex').slice(0, 12)
201-
return `datadog_rum_slim_${hash}`
202-
}
203-
204-
function writeGeneratedStaticResource(resourceName: string, bundle: Buffer) {
205-
rmSync(generatedStaticResourcesDir, { recursive: true, force: true })
206-
mkdirSync(generatedStaticResourcesDir, { recursive: true })
207-
writeFileSync(resolve(generatedStaticResourcesDir, `${resourceName}.js`), bundle)
208-
writeFileSync(
209-
resolve(generatedStaticResourcesDir, `${resourceName}.resource-meta.xml`),
210-
`<?xml version="1.0" encoding="UTF-8" ?>
211-
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
212-
<cacheControl>Private</cacheControl>
213-
<contentType>application/javascript</contentType>
214-
</StaticResource>
215-
`
216-
)
217-
}
218-
219-
function readResourceName(): string {
220-
try {
221-
return readFileSync(resourceNamePath, 'utf8').trim()
222-
} catch {
223-
throw new Error('SF LWC resource name not found. Run `yarn salesforce:deploy-bundle` first.')
224-
}
225-
}

test/apps/sf-lwc-app/.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
.sf
22
.sfdx
3-
.sf-e2e
4-
/force-app/main/default/staticresources/datadog_rum_slim.js
3+
/force-app/main/default/staticresources/datadog_rum_slim.js

test/apps/sf-lwc-app/README.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This app is Lightning-only.
1515

1616
The Salesforce flow uses the Salesforce CLI. Authenticate once, then deploy/open/test through the CLI alias.
1717

18-
Credentials are set as CI variables.
18+
Credentials are set as CI variables.
1919

2020
For local overrides, set the matching environment variables from the repository root:
2121

@@ -32,8 +32,6 @@ Then authenticate the default `sf-lwc-ci` alias:
3232
node scripts/salesforce-lwc-app.ts auth
3333
```
3434

35-
All Salesforce commands default to `sf-lwc-ci`. To use another authenticated alias, set `SF_TARGET_ORG` for the command.
36-
3735
## Initial App Deploy
3836

3937
The full app deploy is only needed when Salesforce metadata changes or the org is being prepared from scratch:
@@ -44,21 +42,21 @@ yarn salesforce:deploy-app
4442

4543
This builds the local RUM slim bundle, copies it to the stable `datadog_rum_slim` static resource, deploys the app metadata, and assigns the `SF_LWC_App` permission set.
4644

47-
## Bundle Deploy
45+
## Local Bundle
4846

49-
After the app exists in the org, deploy only the current RUM slim bundle with:
47+
After the app exists in the org, regular test runs do not deploy the current SDK bundle to Salesforce.
48+
Build the test apps from the repository root instead:
5049

5150
```sh
52-
yarn salesforce:deploy-bundle
51+
yarn build:apps --app sf-lwc-app
5352
```
5453

55-
This builds the local RUM slim bundle, deploys it as a content-hashed static resource, for example `datadog_rum_slim_012345abcdef`, and writes that resource name to `.sf-e2e/resource-name`.
56-
57-
Tests can load a different deployed bundle by opening Salesforce with `c__datadogResourceName=<resourceName>` in the query string. If the query parameter is absent, `c:datadogInit` falls back to the stable `datadog_rum_slim` static resource.
54+
This copies the locally built RUM slim bundle into the ignored stable `datadog_rum_slim` static resource file.
55+
Playwright fulfills Salesforce static resource requests with this local file during E2E tests.
5856

5957
## Open The App
6058

61-
Open the app with the current hashed resource:
59+
Open the app with the E2E RUM configuration:
6260

6361
```sh
6462
yarn salesforce:open
@@ -67,7 +65,7 @@ yarn salesforce:open
6765
The printed URL is authenticated and should be treated as sensitive. It opens the app with RUM query parameters shaped like:
6866

6967
```text
70-
/lightning/app/c__SF_LWC_App/page/home?c__datadogResourceName=<resourceName>&c__applicationId=<applicationId>&c__clientToken=<clientToken>&c__env=dev&c__service=browser-sdk-salesforce-e2e&c__site=datadoghq.com
68+
/lightning/app/c__SF_LWC_App/page/home?c__datadogInitConfiguration=<configuration>
7169
```
7270

7371
To open the app without the generated query parameters:
@@ -78,13 +76,14 @@ sf org open --target-org sf-lwc-ci --path /lightning/app/c__SF_LWC_App/page/home
7876

7977
## Run E2E Tests
8078

81-
Deploy the current bundle, then run the Salesforce scenario:
79+
Build the SDK and test apps, then run the Salesforce scenario:
8280

8381
```sh
84-
yarn salesforce:deploy-bundle
82+
yarn build
83+
yarn build:apps --app sf-lwc-app
8584
FORCE_COLOR=1 PW_BROWSER=chromium yarn test:e2e --project=chromium --grep @salesforce
8685
```
8786

8887
## CI
8988

90-
The `salesforce-e2e` job retrieves Salesforce credentials through `scripts/lib/secrets.ts`, authenticates `sf-lwc-ci`, deploys only the generated hashed bundle, and runs the `@salesforce` Playwright scenario. The regular `e2e` matrix excludes `@salesforce`.
89+
The regular `e2e` job runs the Salesforce scenario in the Chromium matrix entry only. It authenticates `sf-lwc-ci`, opens the deployed Salesforce app, and serves the locally built SDK bundle through Playwright route fulfillment instead of deploying a bundle to Salesforce for each run.

test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const DATADOG_PARAMS = [
1010
'c__applicationId',
1111
'c__clientToken',
1212
'c__datadogInitConfiguration',
13-
'c__datadogResourceName',
1413
'c__env',
1514
'c__service',
1615
'c__site',
@@ -75,10 +74,8 @@ export default class DatadogInit extends NavigationMixin(LightningElement) {
7574

7675
loadDatadogRum() {
7776
const searchParams = new URLSearchParams(window.location.search)
78-
const resourceName = searchParams.get('c__datadogResourceName')
79-
const resourceUrl = resourceName ? `/resource/${encodeURIComponent(resourceName)}` : datadogRumSlim
8077

81-
return loadScript(this, resourceUrl).then(() => {
78+
return loadScript(this, datadogRumSlim).then(() => {
8279
const initConfig = this.getInitConfiguration(searchParams)
8380
if (!initConfig.applicationId || !initConfig.clientToken) {
8481
window.console.warn('Datadog RUM not initialized: missing applicationId or clientToken')

test/e2e/lib/framework/createTest.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { readFile } from 'node:fs/promises'
12
import type { LogsInitConfiguration } from '@datadog/browser-logs'
23
import type { DebuggerInitConfiguration } from '@datadog/browser-debugger'
34
import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core'
@@ -21,7 +22,7 @@ import {
2122
VUE_ROUTER_APP_PORT,
2223
VUE_ROUTER_V4_APP_PORT,
2324
} from '../helpers/playwright'
24-
import { buildSalesforceLwcUrl } from '../helpers/salesforceApp'
25+
import { buildSalesforceLwcUrl, salesforceLwcBundlePath } from '../helpers/salesforceApp'
2526
import { IntakeRegistry } from './intakeRegistry'
2627
import { flushEvents } from './flushEvents'
2728
import type { Servers } from './httpServers'
@@ -453,6 +454,12 @@ function declareTest(title: string, setupOptions: SetupOptions, factory: SetupFa
453454
servers.crossOrigin.bindServerApp(createMockServerApp(servers, setup))
454455

455456
if (setupOptions.salesforceApp) {
457+
await page.route(/\/resource(?:\/[^/?#]+)?\/datadog_rum_slim(?:\.js)?(?:[/?#].*)?$/, async (route) => {
458+
await route.fulfill({
459+
body: await readFile(salesforceLwcBundlePath),
460+
contentType: 'application/javascript',
461+
})
462+
})
456463
await page.route('*/**/api/v2/rum**', async (route) => {
457464
const request = route.request()
458465
const infos = computeIntakeRequestInfos(request)

test/e2e/lib/helpers/salesforceApp.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { resolve } from 'node:path'
33

44
const repositoryRoot = resolve(__dirname, '../../../..')
55

6+
export const salesforceLwcBundlePath = resolve(
7+
repositoryRoot,
8+
'test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.js'
9+
)
10+
611
export function buildSalesforceLwcUrl(): string {
712
const result = spawnSync('node', ['scripts/salesforce-lwc-app.ts', 'open-url'], {
813
encoding: 'utf8',

0 commit comments

Comments
 (0)