Skip to content

Commit fd2290e

Browse files
committed
test(browser): execute packed package in Chromium
1 parent 1eaaf10 commit fd2290e

10 files changed

Lines changed: 225 additions & 76 deletions

File tree

scripts/test-package-install.sh

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ cd "$REPO_ROOT/packages/browser"
3232

3333
# Save original package.json and yarn.lock
3434
cp package.json package.json.backup
35-
35+
cp "$REPO_ROOT/yarn.lock" "$REPO_ROOT/yarn.lock.backup"
3636

3737
# Install the tarball temporarily
3838
yarn add "@datadog/flagging-core@file:$TEST_APP_DIR/core.tgz" --silent
@@ -45,6 +45,8 @@ yarn pack --filename "$TEST_APP_DIR/browser.tgz" > /dev/null 2>&1
4545
echo "Restoring browser package..."
4646
rm package.json
4747
mv package.json.backup package.json
48+
rm "$REPO_ROOT/yarn.lock"
49+
mv "$REPO_ROOT/yarn.lock.backup" "$REPO_ROOT/yarn.lock"
4850

4951
cd "$REPO_ROOT"
5052

@@ -67,8 +69,19 @@ yarn add @datadog/openfeature-browser@file:./browser.tgz --silent
6769
echo "Building test app..."
6870
yarn build
6971

72+
# Execute the built application in a real browser runtime
73+
echo "Installing Chromium..."
74+
if [[ "${CI:-}" == "true" ]]; then
75+
yarn playwright install --with-deps --only-shell chromium
76+
else
77+
yarn playwright install --only-shell chromium
78+
fi
79+
80+
echo "Running browser smoke tests..."
81+
yarn test:browser
82+
7083
echo ""
71-
echo "✓ All tests passed! Package can be installed and the app builds successfully."
84+
echo "✓ All tests passed! Package can be installed and executed in Chromium."
7285
echo ""
7386
echo "To run the test app locally:"
7487
echo " cd test-app"

test-app/index.html

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,10 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>DataDog OpenFeature Browser Test App</title>
7-
<link rel="preconnect" href="https://fonts.googleapis.com" />
8-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9-
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600;700&display=swap" rel="stylesheet" />
10-
<style>
11-
body {
12-
font-family: 'Noto Sans', sans-serif;
13-
max-width: 800px;
14-
margin: 40px auto;
15-
padding: 20px;
16-
line-height: 1.6;
17-
}
18-
h1 {
19-
color: #632ca6;
20-
}
21-
.success {
22-
color: #1e8e3e;
23-
}
24-
pre {
25-
background: #f5f5f5;
26-
padding: 15px;
27-
border-radius: 5px;
28-
overflow-x: auto;
29-
}
30-
.details {
31-
margin-top: 20px;
32-
}
33-
</style>
6+
<title>Datadog OpenFeature full browser smoke test</title>
347
</head>
358
<body>
36-
<div id="app"></div>
9+
<pre id="app"></pre>
3710
<script type="module" src="/src/main.ts"></script>
3811
</body>
3912
</html>

test-app/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "tsc && vite build",
9-
"preview": "vite preview"
9+
"preview": "vite preview",
10+
"test:browser": "playwright test"
1011
},
1112
"dependencies": {
1213
"@datadog/flagging-core": "file:./core.tgz",
@@ -15,6 +16,7 @@
1516
"@openfeature/web-sdk": "^1.7.3"
1617
},
1718
"devDependencies": {
19+
"@playwright/test": "^1.62.1",
1820
"typescript": "^5.9.3",
1921
"vite": "^8.1.3"
2022
}

test-app/playwright.config.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { defineConfig, devices } from '@playwright/test'
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
outputDir: './node_modules/.cache/playwright-test',
6+
reporter: 'line',
7+
workers: 1,
8+
use: {
9+
baseURL: 'http://127.0.0.1:4173',
10+
},
11+
webServer: {
12+
command: 'yarn preview --host 127.0.0.1 --port 4173',
13+
url: 'http://127.0.0.1:4173',
14+
reuseExistingServer: false,
15+
},
16+
projects: [
17+
{
18+
name: 'chromium',
19+
use: { ...devices['Desktop Chrome'] },
20+
},
21+
],
22+
})

test-app/precomputed.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Datadog OpenFeature precomputed browser smoke test</title>
7+
</head>
8+
<body>
9+
<pre id="app"></pre>
10+
<script type="module" src="/src/precomputed.ts"></script>
11+
</body>
12+
</html>

test-app/src/main.ts

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,55 @@
1-
import { DatadogProvider } from '@datadog/openfeature-browser'
2-
import { OpenFeature } from '@openfeature/web-sdk'
3-
4-
// Initialize the Datadog provider
5-
const provider = new DatadogProvider({
6-
applicationId: 'test-app-id',
7-
clientToken: 'test-token',
8-
site: 'datadoghq.com',
9-
service: 'test-service',
1+
import { evaluateRulesBasedConfiguration, matchesRule, OperatorType } from '@datadog/flagging-core'
2+
import { configurationFromString, configurationToString } from '@datadog/openfeature-browser'
3+
import { assert, reportSuccess } from './smoke'
4+
5+
const rulesResponse =
6+
'EgRwcm9kGigKDGJyb3dzZXItZmxhZxIYEAQaAigBIhAKCmFsbG9jYXRpb24iAiADGigKDGludGVnZXItZmxhZxIYEAIaAhgqIhAKCmFsbG9jYXRpb24iAiADKgJvbg=='
7+
const configuration = configurationFromString(JSON.stringify({ version: 1, rules: { response: rulesResponse } }))
8+
9+
assert(configuration.rules, 'rules configuration was not parsed')
10+
11+
const context = { targetingKey: 'browser-user' }
12+
const booleanDetails = evaluateRulesBasedConfiguration(
13+
configuration.rules.response,
14+
'boolean',
15+
'browser-flag',
16+
false,
17+
context,
18+
console
19+
)
20+
const integerDetails = evaluateRulesBasedConfiguration(
21+
configuration.rules.response,
22+
'number',
23+
'integer-flag',
24+
0,
25+
context,
26+
console
27+
)
28+
const restored = configurationFromString(configurationToString(configuration))
29+
const sha256Matched = matchesRule(
30+
{
31+
conditions: [
32+
{
33+
attribute: 'name',
34+
operator: OperatorType.ONE_OF_SHA256,
35+
value: {
36+
salt: [1, 2],
37+
hashes: ['c0e551d80aa1e2cb1eaf5be7edbb04e51eb1823e562e2ce5dfeda0ecba76c744'],
38+
},
39+
},
40+
],
41+
},
42+
{ name: 'hello' }
43+
)
44+
45+
assert(booleanDetails.value === true, 'boolean rule evaluation returned the wrong value')
46+
assert(integerDetails.value === 42, 'integer rule evaluation returned the wrong value')
47+
assert(restored.rules?.response.flags['browser-flag'], 'rules configuration did not survive a round trip')
48+
assert(sha256Matched, 'SHA-256 condition did not match')
49+
50+
reportSuccess({
51+
entrypoint: 'full',
52+
booleanValue: booleanDetails.value,
53+
integerValue: integerDetails.value,
54+
sha256Matched,
1055
})
11-
12-
// Set the provider
13-
OpenFeature.setProvider(provider)
14-
15-
// Get a client
16-
const client = OpenFeature.getClient()
17-
18-
// Evaluate a flag using getBooleanDetails to get full evaluation details
19-
const details = client.getBooleanDetails('test-flag', false)
20-
21-
console.log('✓ Successfully imported and initialized @datadog/openfeature-browser')
22-
console.log('Flag evaluation details:', details)
23-
24-
// Format the details for display
25-
const detailsJson = JSON.stringify(details, null, 2)
26-
27-
// Update the DOM to show success
28-
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
29-
<div>
30-
<h1>DataDog OpenFeature Browser Test App</h1>
31-
<p class="success">✓ Successfully imported and initialized @datadog/openfeature-browser</p>
32-
33-
<div class="details">
34-
<h2>Flag Evaluation Details</h2>
35-
<p><strong>Flag Key:</strong> ${details.flagKey}</p>
36-
<p><strong>Value:</strong> ${details.value}</p>
37-
<p><strong>Reason:</strong> ${details.reason}</p>
38-
<p><strong>Variant:</strong> ${details.variant || 'N/A'}</p>
39-
<p><strong>Error Code:</strong> ${details.errorCode || 'N/A'}</p>
40-
41-
<h3>Full Details (JSON):</h3>
42-
<pre>${detailsJson}</pre>
43-
</div>
44-
</div>
45-
`

test-app/src/precomputed.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { configurationFromString, configurationToString } from '@datadog/openfeature-browser/precomputed'
2+
import { assert, reportSuccess } from './smoke'
3+
4+
const response = {
5+
data: {
6+
attributes: {
7+
createdAt: '2026-07-06T23:01:56.822Z',
8+
flags: {
9+
'precomputed-flag': {
10+
allocationKey: 'allocation',
11+
variationKey: 'on',
12+
variationType: 'BOOLEAN',
13+
variationValue: true,
14+
reason: 'STATIC',
15+
doLog: true,
16+
},
17+
},
18+
},
19+
},
20+
}
21+
const configuration = configurationFromString(
22+
JSON.stringify({
23+
version: 1,
24+
precomputed: { response: JSON.stringify(response) },
25+
rules: { response: 'ignored by the precomputed entrypoint' },
26+
})
27+
)
28+
const roundTrip = JSON.parse(configurationToString(configuration)) as Record<string, unknown>
29+
const flag = configuration.precomputed?.response.data.attributes.flags['precomputed-flag']
30+
31+
assert(flag?.variationValue === true, 'precomputed flag was not parsed')
32+
assert(configuration.rules === undefined, 'precomputed entrypoint parsed rules')
33+
assert(roundTrip.rules === undefined, 'precomputed entrypoint serialized rules')
34+
35+
reportSuccess({
36+
entrypoint: 'precomputed',
37+
booleanValue: flag.variationValue,
38+
rulesExcluded: true,
39+
})

test-app/src/smoke.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function assert(condition: unknown, message: string): asserts condition {
2+
if (!condition) throw new Error(message)
3+
}
4+
5+
export function reportSuccess(result: Record<string, unknown>): void {
6+
const global = globalThis as typeof globalThis & { __OPENFEATURE_SMOKE_RESULT__?: Record<string, unknown> }
7+
global.__OPENFEATURE_SMOKE_RESULT__ = result
8+
9+
const app = document.querySelector<HTMLDivElement>('#app')
10+
if (app) app.textContent = JSON.stringify(result, null, 2)
11+
}

test-app/tests/smoke.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { expect, type Page, test } from '@playwright/test'
2+
3+
type SmokeResult = Record<string, unknown>
4+
5+
async function runSmoke(page: Page, path: string): Promise<SmokeResult> {
6+
const runtimeErrors: string[] = []
7+
page.on('console', (message) => {
8+
if (message.type() === 'error') runtimeErrors.push(`console.error: ${message.text()}`)
9+
})
10+
page.on('pageerror', (error) => runtimeErrors.push(`page error: ${error.message}`))
11+
12+
await page.goto(path)
13+
14+
expect(runtimeErrors).toEqual([])
15+
const result = await page.evaluate(
16+
() =>
17+
(
18+
globalThis as typeof globalThis & {
19+
__OPENFEATURE_SMOKE_RESULT__?: SmokeResult
20+
}
21+
).__OPENFEATURE_SMOKE_RESULT__
22+
)
23+
expect(result).toBeDefined()
24+
return result!
25+
}
26+
27+
test('executes the packed full entrypoint in Chromium', async ({ page }) => {
28+
const result = await runSmoke(page, '/')
29+
30+
expect(result).toEqual({
31+
entrypoint: 'full',
32+
booleanValue: true,
33+
integerValue: 42,
34+
sha256Matched: true,
35+
})
36+
})
37+
38+
test('executes the packed full entrypoint without native text or bigint globals', async ({ page }) => {
39+
await page.addInitScript(() => {
40+
Object.assign(globalThis, {
41+
BigInt: undefined,
42+
TextDecoder: undefined,
43+
TextEncoder: undefined,
44+
})
45+
})
46+
47+
const result = await runSmoke(page, '/')
48+
expect(result.booleanValue).toBe(true)
49+
expect(result.integerValue).toBe(42)
50+
})
51+
52+
test('executes the packed precomputed entrypoint in Chromium', async ({ page }) => {
53+
const result = await runSmoke(page, '/precomputed.html')
54+
55+
expect(result).toEqual({
56+
entrypoint: 'precomputed',
57+
booleanValue: true,
58+
rulesExcluded: true,
59+
})
60+
})

test-app/vite.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
import { fileURLToPath } from 'node:url'
12
import { defineConfig } from 'vite'
23

34
export default defineConfig({
45
build: {
56
target: 'es2015',
7+
rollupOptions: {
8+
input: {
9+
full: fileURLToPath(new URL('./index.html', import.meta.url)),
10+
precomputed: fileURLToPath(new URL('./precomputed.html', import.meta.url)),
11+
},
12+
},
613
},
714
})

0 commit comments

Comments
 (0)