Skip to content
Merged
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
38 changes: 32 additions & 6 deletions scripts/test-package-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get the repo root (parent of scripts/)
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BROWSER_PACKAGE_JSON="$REPO_ROOT/packages/browser/package.json"
BROWSER_PACKAGE_JSON_BACKUP="$BROWSER_PACKAGE_JSON.backup"
ROOT_YARN_LOCK="$REPO_ROOT/yarn.lock"
ROOT_YARN_LOCK_BACKUP="$ROOT_YARN_LOCK.backup"

restore_package_files() {
if [[ -f "$BROWSER_PACKAGE_JSON_BACKUP" ]]; then
mv -f "$BROWSER_PACKAGE_JSON_BACKUP" "$BROWSER_PACKAGE_JSON"
fi
if [[ -f "$ROOT_YARN_LOCK_BACKUP" ]]; then
mv -f "$ROOT_YARN_LOCK_BACKUP" "$ROOT_YARN_LOCK"
fi
}

trap restore_package_files EXIT

echo "Testing @datadog/openfeature-browser package installation..."
echo "Repository root: $REPO_ROOT"
Expand All @@ -31,8 +46,8 @@ echo "Installing packed core into browser package..."
cd "$REPO_ROOT/packages/browser"

# Save original package.json and yarn.lock
cp package.json package.json.backup

cp "$BROWSER_PACKAGE_JSON" "$BROWSER_PACKAGE_JSON_BACKUP"
cp "$ROOT_YARN_LOCK" "$ROOT_YARN_LOCK_BACKUP"

# Install the tarball temporarily
yarn add "@datadog/flagging-core@file:$TEST_APP_DIR/core.tgz" --silent
Expand All @@ -41,10 +56,10 @@ yarn add "@datadog/flagging-core@file:$TEST_APP_DIR/core.tgz" --silent
echo "Packing @datadog/openfeature-browser..."
yarn pack --filename "$TEST_APP_DIR/browser.tgz" > /dev/null 2>&1

# Restore browser package to original state using git
# Restore browser package to original state
echo "Restoring browser package..."
rm package.json
mv package.json.backup package.json
restore_package_files
trap - EXIT

cd "$REPO_ROOT"

Expand All @@ -67,8 +82,19 @@ yarn add @datadog/openfeature-browser@file:./browser.tgz --silent
echo "Building test app..."
yarn build

# Execute the built application in a real browser runtime
echo "Installing Chromium..."
if [[ "${CI:-}" == "true" ]]; then
yarn playwright install --with-deps --only-shell chromium
else
yarn playwright install --only-shell chromium
fi

echo "Running browser smoke tests..."
yarn test:browser

echo ""
echo "✓ All tests passed! Package can be installed and the app builds successfully."
echo "✓ All tests passed! Package can be installed and executed in Chromium."
echo ""
echo "To run the test app locally:"
echo " cd test-app"
Expand Down
33 changes: 3 additions & 30 deletions test-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DataDog OpenFeature Browser Test App</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600;700&display=swap" rel="stylesheet" />
<style>
body {
font-family: 'Noto Sans', sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
line-height: 1.6;
}
h1 {
color: #632ca6;
}
.success {
color: #1e8e3e;
}
pre {
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
.details {
margin-top: 20px;
}
</style>
<title>Datadog OpenFeature protobuf browser smoke test</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<pre id="app"></pre>
<script type="module" src="/src/protobuf.ts"></script>
</body>
</html>
4 changes: 3 additions & 1 deletion test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"test:browser": "playwright test"
},
"dependencies": {
"@datadog/flagging-core": "file:./core.tgz",
Expand All @@ -15,6 +16,7 @@
"@openfeature/web-sdk": "^1.7.3"
},
"devDependencies": {
"@playwright/test": "^1.62.1",
"typescript": "^5.9.3",
"vite": "^8.1.3"
}
Expand Down
22 changes: 22 additions & 0 deletions test-app/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
testDir: './tests',
outputDir: './node_modules/.cache/playwright-test',
reporter: 'line',
workers: 1,
use: {
baseURL: 'http://127.0.0.1:4173',
},
webServer: {
command: 'yarn preview --host 127.0.0.1 --port 4173',
url: 'http://127.0.0.1:4173',
reuseExistingServer: false,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
12 changes: 12 additions & 0 deletions test-app/precomputed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Datadog OpenFeature precomputed browser smoke test</title>
</head>
<body>
<pre id="app"></pre>
<script type="module" src="/src/precomputed.ts"></script>
</body>
</html>
45 changes: 0 additions & 45 deletions test-app/src/main.ts

This file was deleted.

43 changes: 43 additions & 0 deletions test-app/src/precomputed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { configurationFromString, configurationToString } from '@datadog/openfeature-browser/precomputed'
import { assert, reportSuccess } from './smoke'

const response = {
data: {
attributes: {
createdAt: '2026-07-06T23:01:56.822Z',
flags: {
'precomputed-flag': {
allocationKey: 'allocation',
variationKey: 'on',
variationType: 'BOOLEAN',
variationValue: true,
reason: 'STATIC',
doLog: true,
},
},
},
},
}
const configuration = configurationFromString(
JSON.stringify({
version: 1,
precomputed: { response: JSON.stringify(response) },
rules: { response: 'ignored by the precomputed entrypoint' },
})
)
Comment thread
leoromanovsky marked this conversation as resolved.
const serialized = configurationToString(configuration)
const roundTripWire = JSON.parse(serialized) as Record<string, unknown>
const restored = configurationFromString(serialized)
const flag = configuration.precomputed?.response.data.attributes.flags['precomputed-flag']
const restoredFlag = restored.precomputed?.response.data.attributes.flags['precomputed-flag']

assert(flag?.variationValue === true, 'precomputed flag was not parsed')
assert(configuration.rules === undefined, 'precomputed entrypoint parsed rules')
assert(roundTripWire.rules === undefined, 'precomputed entrypoint serialized rules')
assert(restoredFlag?.variationValue === true, 'precomputed flag did not survive a round trip')

reportSuccess({
entrypoint: 'precomputed',
booleanValue: restoredFlag.variationValue,
rulesExcluded: true,
})
98 changes: 98 additions & 0 deletions test-app/src/protobuf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { evaluateRulesBasedConfiguration, matchesRule, OperatorType } from '@datadog/flagging-core'
import { configurationFromString, configurationToString, DatadogProvider } from '@datadog/openfeature-browser'
import { assert, reportSuccess } from './smoke'

const protobufRulesResponse =
'EgRwcm9kGigKDGJyb3dzZXItZmxhZxIYEAQaAigBIhAKCmFsbG9jYXRpb24iAiADGigKDGludGVnZXItZmxhZxIYEAIaAhgqIhAKCmFsbG9jYXRpb24iAiADKgJvbg=='
const precomputedResponse = {
data: {
attributes: {
createdAt: '2026-07-06T23:01:56.822Z',
flags: {
'provider-flag': {
allocationKey: 'allocation',
variationKey: 'on',
variationType: 'BOOLEAN',
variationValue: true,
reason: 'STATIC',
doLog: false,
},
},
},
},
}
const configuration = configurationFromString(
JSON.stringify({
version: 1,
precomputed: { response: JSON.stringify(precomputedResponse) },
rules: { response: protobufRulesResponse },
})
)

assert(configuration.rules, 'protobuf rules configuration was not decoded')
assert(
configuration.rules.response.$typeName === 'datadog.ffe.flagging.ufc.v1.FlagsConfiguration',
'rules response is not a generated protobuf message'
)

const context = { targetingKey: 'browser-user' }
const booleanDetails = evaluateRulesBasedConfiguration(
configuration.rules.response,
'boolean',
'browser-flag',
false,
context,
console
)
const integerDetails = evaluateRulesBasedConfiguration(
configuration.rules.response,
'number',
'integer-flag',
0,
context,
console
)
const provider = new DatadogProvider({
clientToken: 'test-token',
site: 'datadoghq.com',
env: 'test',
enableExposureLogging: false,
enableFlagEvaluationTracking: false,
enableRumFeatureFlagTracking: false,
initialFlagsConfiguration: configuration,
})
const providerDetails = provider.resolveBooleanEvaluation('provider-flag', false, context, console)
const restored = configurationFromString(configurationToString(configuration))
const sha256Matched = matchesRule(
{
conditions: [
{
attribute: 'name',
operator: OperatorType.ONE_OF_SHA256,
value: {
salt: [1, 2],
hashes: ['c0e551d80aa1e2cb1eaf5be7edbb04e51eb1823e562e2ce5dfeda0ecba76c744'],
},
},
],
},
{ name: 'hello' }
)

assert(booleanDetails.value === true, 'protobuf boolean evaluation returned the wrong value')
assert(integerDetails.value === 42, 'protobuf int64 evaluation returned the wrong value')
assert(providerDetails.value === true, 'DatadogProvider did not evaluate the decoded configuration')
assert(
restored.rules?.response.$typeName === 'datadog.ffe.flagging.ufc.v1.FlagsConfiguration',
'protobuf rules configuration did not survive a round trip'
)
assert(sha256Matched, 'SHA-256 condition did not match')

reportSuccess({
entrypoint: 'protobuf',
protobufTypeName: configuration.rules.response.$typeName,
booleanValue: booleanDetails.value,
integerValue: integerDetails.value,
providerValue: providerDetails.value,
sha256Matched,
})
11 changes: 11 additions & 0 deletions test-app/src/smoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message)
}

export function reportSuccess(result: Record<string, unknown>): void {
const global = globalThis as typeof globalThis & { __OPENFEATURE_SMOKE_RESULT__?: Record<string, unknown> }
global.__OPENFEATURE_SMOKE_RESULT__ = result

const app = document.querySelector<HTMLDivElement>('#app')
if (app) app.textContent = JSON.stringify(result, null, 2)
}
Loading