Skip to content

Commit bcef211

Browse files
test: add electron contract tests (#1140)
This PR will: - add contract test implementation to Electron - add contract test runs into electron GHA workflow <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk due to new CI jobs and scheduled runs that spin up Electron/Xvfb and execute the shared contract-test harness, which can introduce flakiness and longer runtimes. Changes are mostly test-only but add new dependencies and AWS-secret retrieval for e2e example runs. > > **Overview** > Adds **Electron SDK contract testing** by introducing a new `packages/sdk/electron/contract-tests/entity` Electron app that exposes the SDK test-harness REST protocol (create client, run commands, delete client, shutdown) and maps harness config into `LDOptions` (endpoints, events, TLS, hooks, etc.). > > Updates the Electron GitHub Actions workflow to run nightly, build and launch the contract-test entity headlessly via Xvfb/Playwright, wait for port `8000`, and then run the `launchdarkly/gh-actions` contract tests (with `suppressions.txt`). Also adds a new `run-example` CI job that builds the Electron example and runs Playwright e2e using AWS-sourced test keys. > > Extends the Electron example with a Playwright-based smoke test, adds `build`/`test` scripts and Playwright deps, and tweaks `.gitignore` to capture `test-results/`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 814d2b5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: semgrep-code-launchdarkly[bot] <167133144+semgrep-code-launchdarkly[bot]@users.noreply.github.com>
1 parent ab2436d commit bcef211

26 files changed

Lines changed: 1278 additions & 30 deletions

.github/workflows/electron.yaml

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@ on:
99
branches: [main, 'feat/**']
1010
paths-ignore:
1111
- '**.md'
12-
# TODO: re-enable this when we set up the example tests.
13-
# schedule:
14-
# - cron: '0 0 * * *'
12+
schedule:
13+
- cron: '0 0 * * *'
1514

1615
jobs:
1716
build-test-electron:
1817
# Skip this job on scheduled runs to avoid redundant runs.
19-
# TODO: re-enable this when we set up the example tests.
20-
# if: github.event_name != 'schedule'
18+
if: github.event_name != 'schedule'
2119
runs-on: ubuntu-latest
2220
strategy:
2321
matrix:
@@ -36,29 +34,78 @@ jobs:
3634
workspace_name: '@launchdarkly/electron-client-sdk'
3735
workspace_path: packages/sdk/electron
3836

39-
# TODO: Add example tests - we will do this in tandem with the contract tests
40-
# as they will need to have a similar kind of setup.
41-
# run-example:
42-
# runs-on: ubuntu-latest
43-
# permissions:
44-
# id-token: write
45-
# steps:
46-
# - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
47-
# - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
48-
# with:
49-
# node-version: 24
50-
# - name: Setup Yarn
51-
# run: yarn set version 3.4.1
52-
# - name: Install dependencies
53-
# run: yarn workspaces focus @internal/electron-example
54-
# - name: Build SDK dependencies
55-
# run: yarn workspaces foreach -pR --topological-dev --from '@internal/electron-example' run build
56-
# - name: Install Xvfb
57-
# run: sudo apt-get install -y xvfb
58-
# - uses: launchdarkly/gh-actions/actions/verify-hello-app@verify-hello-app-v1.0.0
59-
# with:
60-
# use_mobile_key: true
61-
# role_arn: ${{ vars.AWS_ROLE_ARN }}
62-
# command: xvfb-run -a yarn workspace @internal/electron-example start -- --no-sandbox
37+
# Contract Tests
38+
- name: Install contract test dependencies
39+
env:
40+
ELECTRON_DISABLE_SANDBOX: '1'
41+
run: |
42+
yarn workspaces focus @internal/electron-contract-tests-entity
43+
yarn workspace @internal/electron-contract-tests-entity build
44+
sudo apt-get install -y xvfb
45+
Xvfb :99 -screen 0 1024x768x24 > /tmp/xvfb.log 2>&1 &
6346
64-
# TODO: Add contract tests
47+
- name: run contract test entity
48+
env:
49+
ELECTRON_DISABLE_SANDBOX: '1'
50+
DISPLAY: ':99'
51+
run: |
52+
yarn workspace @internal/electron-contract-tests-entity open-electron &> /tmp/http-server.log &
53+
echo "Waiting for contract test entity on port 8000..."
54+
for i in {1..30}; do
55+
if curl -s http://localhost:8000 > /dev/null; then
56+
echo "Contract test entity ready"
57+
break
58+
fi
59+
if [ $i -eq 30 ]; then
60+
echo "Timeout waiting for contract test entity"
61+
cat /tmp/http-server.log
62+
exit 1
63+
fi
64+
sleep 1
65+
done
66+
67+
- name: Run contract tests
68+
uses: launchdarkly/gh-actions/actions/contract-tests@5adb11fd6953e1bc35d9cf1fc1b4374c464e3a8b
69+
with:
70+
test_service_port: 8000
71+
token: ${{ secrets.GITHUB_TOKEN }}
72+
extra_params: '--skip-from=${{ github.workspace }}/packages/sdk/electron/contract-tests/suppressions.txt --stop-service-at-end'
73+
74+
run-example:
75+
runs-on: ubuntu-latest
76+
permissions:
77+
id-token: write
78+
steps:
79+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
80+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
81+
with:
82+
node-version: 24
83+
- name: Install dependencies
84+
run: yarn workspaces focus @internal/electron-example
85+
- name: Build SDK dependencies
86+
run: yarn workspaces foreach -pR --topological-dev --from '@internal/electron-example' run build
87+
- name: Build example app
88+
env:
89+
ELECTRON_DISABLE_SANDBOX: '1'
90+
run: yarn workspace @internal/electron-example build
91+
- name: Setup Xvfb
92+
run: |
93+
sudo apt-get install -y xvfb
94+
Xvfb :99 -screen 0 1024x768x24 > /tmp/xvfb.log 2>&1 &
95+
- name: Install Playwright dependencies
96+
run: yarn workspace @internal/electron-example playwright install --with-deps chromium
97+
- uses: launchdarkly/gh-actions/actions/release-secrets@bbbbbda684f500766264e7fe327668094ba83d1c # release-secrets-v1.2.0
98+
name: 'Get the mobile SDK key'
99+
with:
100+
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
101+
ssm_parameter_pairs: '/sdk/common/hello-apps/mobile-key = LAUNCHDARKLY_MOBILE_KEY'
102+
- uses: launchdarkly/gh-actions/actions/release-secrets@bbbbbda684f500766264e7fe327668094ba83d1c # release-secrets-v1.2.0
103+
name: 'Get the test feature flag key'
104+
with:
105+
aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
106+
ssm_parameter_pairs: '/sdk/common/hello-apps/boolean-flag-key = LAUNCHDARKLY_FLAG_KEY'
107+
- name: Run e2e tests
108+
env:
109+
ELECTRON_DISABLE_SANDBOX: '1'
110+
DISPLAY: ':99'
111+
run: yarn workspace @internal/electron-example test

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"packages/sdk/cloudflare/example",
1616
"packages/sdk/electron",
1717
"packages/sdk/electron/example",
18+
"packages/sdk/electron/contract-tests/entity",
1819
"packages/sdk/fastly",
1920
"packages/sdk/fastly/example",
2021
"packages/sdk/react",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Electron SDK Contract Tests
2+
3+
This directory contains the contract test implementation for the LaunchDarkly Electron SDK using the [SDK Test Harness](https://github.com/launchdarkly/sdk-test-harness).
4+
5+
## Architecture
6+
7+
The Electron contract tests use a single component:
8+
9+
**Entity** (`entity/`): An Electron application that:
10+
11+
- Runs an Express server in the **main process** on port 8000
12+
- Exposes a REST API that the test harness calls directly (no separate adapter)
13+
- Creates and manages Electron SDK clients via `ClientFactory` in response to harness commands
14+
- Implements the contract test protocol: create client (POST /), run commands (POST /clients/:id), delete client (DELETE /clients/:id), shutdown (DELETE /)
15+
16+
The test harness sends HTTP requests to the entity’s REST API. The entity runs the Electron SDK in the main process and responds with evaluation results and other command outputs.
17+
18+
## Running Locally
19+
20+
### Prerequisites
21+
22+
- Node.js 18 or later
23+
- Yarn
24+
- Electron-supported platform (macOS, Windows, or Linux). For headless/CI, a virtual display (e.g. xvfb on Linux) may be required.
25+
26+
### Quick start
27+
28+
1. **Install dependencies** from the repository root:
29+
```bash
30+
yarn
31+
```
32+
33+
2. **Build the entity** so the Electron app has a built main process. From the repository root:
34+
```bash
35+
yarn workspaces foreach -pR --topological-dev --from @internal/electron-contract-tests-entity run build
36+
```
37+
38+
3. **Start the contract test entity** (the Electron app with the REST server on port 8000). From the repository root:
39+
```bash
40+
yarn workspace @internal/electron-contract-tests-entity run start
41+
```
42+
Or from `entity/`:
43+
```bash
44+
yarn start
45+
```
46+
The app window may open; the server runs in the main process. Keep it running while you run the harness.
47+
48+
For a headless run (e.g. CI), use the open-electron script instead:
49+
```bash
50+
yarn workspace @internal/electron-contract-tests-entity run open-electron
51+
```
52+
This launches the built app via Playwright’s Electron API and keeps it running until you press Ctrl+C.
53+
54+
4. **Run the SDK test harness** against the entity. The entity’s REST API is at:
55+
- **Base URL:** http://localhost:8000
56+
57+
Example with a local clone of the test harness:
58+
```bash
59+
go run . --url http://localhost:8000
60+
```
61+
62+
If you have a suppressions file (e.g. for known differences), pass it with `-skip-from`:
63+
```bash
64+
go run . --url http://localhost:8000 -skip-from path-to-js-core/packages/sdk/electron/contract-tests/suppressions.txt
65+
```
66+
67+
More details on the harness: https://github.com/launchdarkly/sdk-test-harness
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
.DS_Store
18+
19+
# Directory for instrumented libs generated by jscoverage/JSCover
20+
lib-cov
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
*.lcov
25+
26+
# nyc test coverage
27+
.nyc_output
28+
29+
# node-waf configuration
30+
.lock-wscript
31+
32+
# Compiled binary addons (https://nodejs.org/api/addons.html)
33+
build/Release
34+
35+
# Dependency directories
36+
node_modules/
37+
jspm_packages/
38+
39+
# TypeScript v1 declaration files
40+
typings/
41+
42+
# TypeScript cache
43+
*.tsbuildinfo
44+
45+
# Optional npm cache directory
46+
.npm
47+
48+
# Optional eslint cache
49+
.eslintcache
50+
51+
# Optional REPL history
52+
.node_repl_history
53+
54+
# Output of 'npm pack'
55+
*.tgz
56+
57+
# Yarn Integrity file
58+
.yarn-integrity
59+
60+
# dotenv environment variables file
61+
.env
62+
.env.test
63+
64+
# parcel-bundler cache (https://parceljs.org/)
65+
.cache
66+
67+
# next.js build output
68+
.next
69+
70+
# nuxt.js build output
71+
.nuxt
72+
73+
# vuepress build output
74+
.vuepress/dist
75+
76+
# Serverless directories
77+
.serverless/
78+
79+
# FuseBox cache
80+
.fusebox/
81+
82+
# DynamoDB Local files
83+
.dynamodb/
84+
85+
# Webpack
86+
.webpack/
87+
88+
# Vite
89+
.vite/
90+
91+
# Electron-Forge
92+
out/
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/* eslint-disable import/no-extraneous-dependencies */
2+
import { MakerDeb } from '@electron-forge/maker-deb';
3+
import { MakerRpm } from '@electron-forge/maker-rpm';
4+
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
5+
import { MakerZIP } from '@electron-forge/maker-zip';
6+
import { FusesPlugin } from '@electron-forge/plugin-fuses';
7+
import { VitePlugin } from '@electron-forge/plugin-vite';
8+
import type { ForgeConfig } from '@electron-forge/shared-types';
9+
import { FuseV1Options, FuseVersion } from '@electron/fuses';
10+
11+
const config: ForgeConfig = {
12+
packagerConfig: {
13+
asar: true,
14+
},
15+
rebuildConfig: {},
16+
makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})],
17+
plugins: [
18+
new VitePlugin({
19+
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
20+
// If you are familiar with Vite configuration, it will look really familiar.
21+
build: [
22+
{
23+
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
24+
entry: 'src/main.ts',
25+
config: 'vite.main.config.ts',
26+
target: 'main',
27+
},
28+
],
29+
renderer: [],
30+
}),
31+
// Fuses are used to enable/disable various Electron functionality
32+
// at package time, before code signing the application
33+
new FusesPlugin({
34+
version: FuseVersion.V1,
35+
[FuseV1Options.RunAsNode]: false,
36+
[FuseV1Options.EnableCookieEncryption]: true,
37+
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
38+
[FuseV1Options.EnableNodeCliInspectArguments]: false,
39+
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
40+
[FuseV1Options.OnlyLoadAppFromAsar]: true,
41+
}),
42+
],
43+
};
44+
45+
export default config;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />
2+
3+
/** Injected at build time from LD_CLIENT_SIDE_ID (see vite.renderer.config.ts). */
4+
// eslint-disable-next-line no-underscore-dangle
5+
declare const __LD_CLIENT_SIDE_ID__: string;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Hello World!</title>
6+
</head>
7+
<body>
8+
<h1>💖 Hello World!</h1>
9+
<p>Welcome to your Electron application.</p>
10+
<script type="module" src="/src/renderer.ts"></script>
11+
</body>
12+
</html>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Opens the Electron app and keeps it running until the process is terminated.
5+
* Run from the entity directory after building (e.g. after running start:debug once).
6+
*
7+
* Usage: node open-electron.mjs
8+
*/
9+
10+
const { _electron: electron } = await import('playwright');
11+
12+
console.log('Launching Electron app...');
13+
14+
let close = null;
15+
16+
const lifetimePromise = new Promise((resolve) => {
17+
close = resolve;
18+
});
19+
20+
const electronApp = await electron.launch({
21+
args: ['.vite/build/main.js'],
22+
cwd: process.cwd(),
23+
});
24+
25+
console.log('Electron contract test entity is running. Press Ctrl+C to close.');
26+
27+
electronApp.on('close', () => {
28+
close();
29+
});
30+
31+
await lifetimePromise;

0 commit comments

Comments
 (0)