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
86 changes: 86 additions & 0 deletions .github/workflows/browser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,89 @@ jobs:
package_name: '@launchdarkly/js-client-sdk'
pr_number: ${{ github.event.number }}
size_limit: 25000

# Contract Tests
- name: Install Playwright browsers
Copy link
Member Author

Choose a reason for hiding this comment

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

I think playwright doesn't like this, but it works fine. Probably could re-order things.

run: npx playwright install --with-deps chromium

- name: Install contract test dependencies
run: yarn workspaces focus browser-contract-test-adapter browser-contract-test-service
Copy link

Choose a reason for hiding this comment

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

Bug: Fix Playwright Workflow Installation Order

The workflow installs Playwright browsers before installing the contract test dependencies that include the playwright package. This causes npx playwright install to potentially download a different version of Playwright than specified in package.json, or fail if the package isn't available. The "Install contract test dependencies" step needs to run before "Install Playwright browsers".

Fix in Cursor Fix in Web


- name: Build contract test adapter
run: yarn workspace browser-contract-test-adapter run build

- name: Build contract test entity (browser app)
run: yarn workspace browser-contract-test-service run build

- name: Start contract test adapter in background
run: |
yarn workspace browser-contract-test-adapter run start > /tmp/adapter.log 2>&1 &
echo $! > /tmp/adapter.pid

- name: Serve browser app with http-server
run: |
npx http-server packages/sdk/browser/contract-tests/entity/dist -p 5173 --cors > /tmp/http-server.log 2>&1 &
echo $! > /tmp/http-server.pid

- name: Wait for services to be ready
run: |
echo "Waiting for adapter on port 8001..."
for i in {1..30}; do
if nc -z localhost 8001; then
echo "Adapter WebSocket ready"
break
fi
if [ $i -eq 30 ]; then
echo "Timeout waiting for adapter"
cat /tmp/adapter.log
exit 1
fi
sleep 1
done

echo "Waiting for HTTP server on port 5173..."
for i in {1..30}; do
if curl -s http://localhost:5173 > /dev/null; then
echo "HTTP server ready"
break
fi
if [ $i -eq 30 ]; then
echo "Timeout waiting for HTTP server"
cat /tmp/http-server.log
exit 1
fi
sleep 1
done

- name: Open browser app in headless Chromium
run: |
node packages/sdk/browser/contract-tests/entity/open-browser.mjs http://localhost:5173 > /tmp/playwright.log 2>&1 &
echo $! > /tmp/playwright.pid
sleep 5 # Give the browser time to initialize and connect via WebSocket

- name: Run contract tests
uses: launchdarkly/gh-actions/actions/contract-tests@21174f3a7f3aa3e3121227ec91842e8a1ebeec6e
with:
test_service_port: 8000
token: ${{ secrets.GITHUB_TOKEN }}
extra_params: '--skip-from=${{ github.workspace }}/packages/sdk/browser/contract-tests/suppressions.txt --stop-service-at-end'

- name: Print logs on failure
if: failure()
run: |
echo "=== Adapter Log ==="
cat /tmp/adapter.log || echo "No adapter log"
echo "=== HTTP Server Log ==="
cat /tmp/http-server.log || echo "No http-server log"
echo "=== Playwright Log ==="
cat /tmp/playwright.log || echo "No playwright log"

- name: Cleanup contract test services
if: always()
run: |
[ -f /tmp/playwright.pid ] && kill $(cat /tmp/playwright.pid) || true
[ -f /tmp/http-server.pid ] && kill $(cat /tmp/http-server.pid) || true
[ -f /tmp/adapter.pid ] && kill $(cat /tmp/adapter.pid) || true
pkill -f "playwright" || true
pkill -f "http-server" || true
pkill -f "browser-contract-test-adapter" || true
53 changes: 53 additions & 0 deletions packages/sdk/browser/contract-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Browser SDK Contract Tests

This directory contains the contract test implementation for the LaunchDarkly Browser SDK using the [SDK Test Harness](https://github.com/launchdarkly/sdk-test-harness).

## Architecture

The browser contract tests consist of three components:

1. **Adapter** (`adapter/`): A Node.js server that:
- Exposes a REST API on port 8000 for the test harness
- Runs a WebSocket server on port 8001 for browser communication
- Translates REST commands to WebSocket messages

2. **Entity** (`entity/`): A browser application (Vite app) that:
- Connects to the adapter via WebSocket
- Implements the actual SDK test logic
- Runs the Browser SDK in a real browser environment

3. **Test Harness**: The SDK test harness that:
- Sends test commands via REST API to the adapter (port 8000)
- Validates SDK behavior across different scenarios

## Running Locally

### Prerequisites

- Node.js 18 or later
- Yarn
- A modern browser (for manual testing)

### Quick Start

```bash
# From the repository root
./packages/sdk/browser/contract-tests/run-test-service.sh
```

This script will:
1. Start the adapter (WebSocket bridge)
2. Start the entity (browser app with Vite dev server)
3. Open the browser app in your default browser

The services will be available at:
- Adapter REST API: http://localhost:8000
- Adapter WebSocket: ws://localhost:8001
- Browser App: http://localhost:5173

You then run the `sdk-test-harness`. More information is available here: https://github.com/launchdarkly/sdk-test-harness

Example with local clone of the test harness:
```bash
go run . --url http://localhost:8123 -skip-from path-to-your-js-core-clone/packages/sdk/browser/contract-tests/suppressions.txt
```
42 changes: 42 additions & 0 deletions packages/sdk/browser/contract-tests/entity/open-browser.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env node

/**
* Opens a headless browser and navigates to the contract test entity page.
* Keeps the browser open until the process is terminated.
*
* Usage: node open-browser.mjs [url]
* Default URL: http://localhost:5173
*/

import { chromium } from 'playwright';

const url = process.argv[2] || 'http://localhost:5173';

console.log(`Opening headless browser at ${url}...`);

const browser = await chromium.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});

const context = await browser.newContext();
const page = await context.newPage();

// Log console messages from the browser
page.on('console', (msg) => {
console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`);
});

// Log page errors
page.on('pageerror', (error) => {
console.error(`[Browser Error] ${error.message}`);
});

await page.goto(url);

console.log('Browser is open and running. Press Ctrl+C to close.');

// Keep the process alive
await new Promise(() => {
// Intentionally never resolve - keeps browser open until process is killed
});
1 change: 1 addition & 0 deletions packages/sdk/browser/contract-tests/entity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.6.3",
"eslint-plugin-prettier": "^5.0.0",
"playwright": "^1.49.1",
"prettier": "^3.0.0",
"typescript": "^5.5.3",
"vite": "^5.4.1"
Expand Down