Skip to content

Commit 4649dc5

Browse files
committed
feat(e2e): init QA starter kit
1 parent 6612316 commit 4649dc5

File tree

9 files changed

+300
-56
lines changed

9 files changed

+300
-56
lines changed

packages/e2e/README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ Utilities and ready-made specs for Lit Protocol end-to-end testing. This package
55
## Installation
66

77
```bash
8-
pnpm add -D jest @lit-protocol/e2e @lit-protocol/lit-client @lit-protocol/networks viem
8+
pnpm add -D @lit-protocol/e2e
99
```
1010

11-
> The package depends on `jest` being available in the consumer workspace. Install any additional peer dependencies (for example `ts-node` if you prefer to author specs in TypeScript directly).
11+
The CLI bundles Jest, Babel presets, and all required helpers. Install any additional project-specific tooling (for example `ts-node` if you prefer to author specs in TypeScript directly).
1212

1313
## Required Environment
1414

@@ -37,12 +37,12 @@ The published package contains the compiled `e2e.spec.ts`. You can execute it ei
3737

3838
```bash
3939
# Preferred: CLI wrapper injects the packaged config automatically
40-
npx lit-e2e
40+
pnpm exec lit-e2e
4141

4242
# Equivalent manual invocation
43-
npx jest \
44-
--config node_modules/@lit-protocol/e2e/dist/jest.e2e.package.config.cjs \
45-
node_modules/@lit-protocol/e2e/dist/specs/e2e.spec.ts
43+
pnpm exec jest \
44+
--config node_modules/@lit-protocol/e2e/jest.e2e.package.config.cjs \
45+
node_modules/@lit-protocol/e2e/specs/e2e.spec.ts
4646
```
4747

4848
Both commands honour additional Jest flags (e.g. `--runInBand`, `--verbose`), so you can tailor runs to your infrastructure.
@@ -73,11 +73,29 @@ describe('Epoch rollover', () => {
7373
Execute custom specs with the same packaged config:
7474

7575
```bash
76-
npx jest --config node_modules/@lit-protocol/e2e/dist/jest.e2e.package.config.cjs qa-epoch.spec.ts
76+
pnpm exec jest --config node_modules/@lit-protocol/e2e/jest.e2e.package.config.cjs qa-epoch.spec.ts
77+
78+
# or add them on the fly with the CLI
79+
pnpm exec lit-e2e --patterns qa-epoch.spec.ts
7780
```
7881

7982
## Bundled APIs
8083

84+
## Optional Local Scaffolding
85+
86+
Prefer to maintain project-local configs? Let the CLI create them for you:
87+
88+
```bash
89+
pnpm exec lit-e2e init
90+
```
91+
92+
This generates:
93+
94+
- `jest.e2e.local.cjs` – a wrapper that runs the packaged suite and your own specs
95+
- `babel.config.cjs` – delegates to the package’s Babel presets
96+
97+
Update your Jest scripts to reference `jest.e2e.local.cjs` if you take this route.
98+
8199
Key exports now available from the package:
82100

83101
- `init(network?, logLevel?)` – prepares Lit Client, Auth Manager, PKPs, and funded accounts across local or live environments.

packages/e2e/babel.config.cjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
presets: [
3+
['@babel/preset-env', { targets: { node: 'current' }, modules: 'commonjs' }],
4+
['@babel/preset-typescript', { allowDeclareFields: true }],
5+
],
6+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const path = require('path');
2+
const baseConfig = require('@lit-protocol/e2e/jest-config');
3+
const packageRoot = path.dirname(require.resolve('@lit-protocol/e2e'));
4+
5+
module.exports = {
6+
...baseConfig,
7+
rootDir: __dirname,
8+
roots: Array.from(new Set([__dirname, packageRoot])),
9+
testMatch: [
10+
...baseConfig.testMatch,
11+
path.join(__dirname, 'specs/**/*.spec.ts'),
12+
path.join(__dirname, '**/*.spec.ts'),
13+
],
14+
};

packages/e2e/bin/run-e2e.cjs

Lines changed: 168 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,180 @@
11
#!/usr/bin/env node
22

33
/**
4-
* Lightweight CLI wrapper that runs Jest with the packaged E2E configuration.
5-
* Consumers can still override CLI flags; the bundled config is injected unless
6-
* `--config` is already provided.
4+
* CLI for @lit-protocol/e2e.
5+
*
6+
* - `lit-e2e` runs the bundled suite with zero local config.
7+
* - `lit-e2e --patterns <glob,glob>` adds extra specs on the fly.
8+
* - `lit-e2e init` scaffolds `jest.e2e.local.cjs` and `babel.config.cjs`
9+
* so teams can hand-roll their own Jest entry-points if they prefer.
710
*/
11+
12+
const fs = require('fs');
13+
const os = require('os');
814
const path = require('path');
15+
const { run } = require('jest');
16+
17+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
18+
const WRAPPER_PATH = path.join(__dirname, 'jest.export-wrapper.cjs');
19+
const BABEL_CONFIG_SRC = path.join(PACKAGE_ROOT, 'babel.config.cjs');
20+
21+
const HELP_TEXT = `
22+
Usage: lit-e2e [options] [-- <jest-args>]
23+
24+
Options:
25+
--patterns, -p <glob[,glob]> Run additional specs (globs resolved from cwd)
26+
init Scaffold jest.e2e.local.cjs & babel.config.cjs
27+
--help, -h Show this help message
28+
29+
Examples:
30+
lit-e2e --runInBand
31+
lit-e2e --patterns qa-epoch.spec.ts -- --runInBand
32+
lit-e2e init
33+
`.trim();
34+
35+
function printHelp() {
36+
console.log(HELP_TEXT);
37+
}
38+
39+
/**
40+
* Parse CLI arguments.
41+
*/
42+
function parseArgs(argv) {
43+
if (argv.includes('init')) {
44+
return { mode: 'init' };
45+
}
46+
if (argv.includes('--help') || argv.includes('-h')) {
47+
return { mode: 'help' };
48+
}
49+
50+
const patterns = [];
51+
const passThrough = [];
52+
53+
for (let i = 0; i < argv.length; i += 1) {
54+
const arg = argv[i];
55+
if (arg === '--patterns' || arg === '-p') {
56+
const value = argv[i + 1];
57+
if (!value) {
58+
throw new Error('Missing value for --patterns');
59+
}
60+
value
61+
.split(',')
62+
.map((item) => item.trim())
63+
.filter(Boolean)
64+
.forEach((item) => patterns.push(item));
65+
i += 1;
66+
continue;
67+
}
68+
passThrough.push(arg);
69+
}
970

10-
let runJest;
11-
try {
12-
runJest = require('jest').run;
13-
} catch (error) {
14-
console.error('❌ Unable to locate Jest. Please install it in your project (e.g. `pnpm add -D jest`).');
15-
process.exit(1);
71+
return { mode: 'run', patterns, passThrough };
1672
}
1773

18-
const defaultConfig = path.join(__dirname, '..', 'jest.e2e.package.config.cjs');
74+
/**
75+
* Normalise extra test patterns.
76+
*/
77+
function normalisePatterns(patterns) {
78+
return patterns.map((pattern) => {
79+
const absolute = path.isAbsolute(pattern)
80+
? pattern
81+
: path.resolve(process.cwd(), pattern);
82+
return absolute.replace(/\\/g, '/');
83+
});
84+
}
1985

20-
const argv = process.argv.slice(2);
21-
const hasConfigFlag = argv.some((arg) => arg === '--config' || arg.startsWith('--config='));
86+
/**
87+
* Create a temporary Jest config that extends the bundled wrapper.
88+
*/
89+
function writeTemporaryConfig(extraPatterns) {
90+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lit-e2e-'));
91+
const configPath = path.join(tmpDir, 'jest.config.cjs');
92+
const source = `
93+
const path = require('path');
94+
const baseConfig = require(${JSON.stringify(WRAPPER_PATH)});
95+
const extra = ${JSON.stringify(extraPatterns)};
2296
23-
if (!hasConfigFlag) {
24-
argv.unshift('--config', defaultConfig);
97+
module.exports = {
98+
...baseConfig,
99+
testMatch: baseConfig.testMatch.concat(extra),
100+
};
101+
`;
102+
fs.writeFileSync(configPath, source);
103+
return { tmpDir, configPath };
25104
}
26105

27-
runJest(argv);
106+
function cleanupTemporaryDir(tmpDir) {
107+
try {
108+
fs.rmSync(tmpDir, { recursive: true, force: true });
109+
} catch (error) {
110+
// Non fatal
111+
}
112+
}
113+
114+
/**
115+
* Scaffold local wrapper/babel config.
116+
*/
117+
function createLocalWrapper() {
118+
const cwd = process.cwd();
119+
const jestDest = path.join(cwd, 'jest.e2e.local.cjs');
120+
const babelDest = path.join(cwd, 'babel.config.cjs');
121+
122+
if (!fs.existsSync(jestDest)) {
123+
fs.copyFileSync(WRAPPER_PATH, jestDest);
124+
console.log(`✅ Created ${path.relative(cwd, jestDest)}`);
125+
} else {
126+
console.warn(`⚠️ ${path.relative(cwd, jestDest)} already exists, skipping`);
127+
}
128+
129+
if (!fs.existsSync(babelDest)) {
130+
fs.copyFileSync(BABEL_CONFIG_SRC, babelDest);
131+
console.log(`✅ Created ${path.relative(cwd, babelDest)}`);
132+
} else {
133+
console.warn(`⚠️ ${path.relative(cwd, babelDest)} already exists, skipping`);
134+
}
135+
136+
console.log('✨ Local scaffolding complete. Point Jest at jest.e2e.local.cjs to run from this project.');
137+
}
138+
139+
/**
140+
* Run Jest using the temporary config.
141+
*/
142+
async function runSuite(patterns, passThrough) {
143+
const normalised = normalisePatterns(patterns);
144+
const configHandle = writeTemporaryConfig(normalised);
145+
146+
try {
147+
const jestArgs = ['--config', configHandle.configPath, ...passThrough];
148+
await run(jestArgs);
149+
} finally {
150+
cleanupTemporaryDir(configHandle.tmpDir);
151+
}
152+
}
153+
154+
(async () => {
155+
let parsed;
156+
try {
157+
parsed = parseArgs(process.argv.slice(2));
158+
} catch (error) {
159+
console.error(`❌ ${error.message || error}`);
160+
printHelp();
161+
process.exit(1);
162+
}
163+
164+
if (parsed.mode === 'help') {
165+
printHelp();
166+
return;
167+
}
168+
169+
if (parsed.mode === 'init') {
170+
createLocalWrapper();
171+
return;
172+
}
173+
174+
try {
175+
await runSuite(parsed.patterns, parsed.passThrough);
176+
} catch (error) {
177+
console.error(error && error.stack ? error.stack : error);
178+
process.exit(1);
179+
}
180+
})();

packages/e2e/jest.e2e.package.config.cjs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
/**
22
* Packaged Jest configuration for consumers of @lit-protocol/e2e.
3-
* Resolves the compiled spec shipped with the package and keeps transforms open
4-
* in case additional TypeScript-based specs are provided by QA teams.
3+
* Resolves the bundled specs and maps helpers so QA can run the suite without
4+
* writing local config or Babel presets manually.
55
*/
6-
const config = {
6+
const path = require('path');
7+
8+
const srcEntry = require.resolve('@lit-protocol/e2e');
9+
const srcDir = path.dirname(srcEntry);
10+
const packageRoot = path.dirname(srcDir);
11+
module.exports = {
712
testEnvironment: 'node',
13+
rootDir: packageRoot,
14+
roots: [packageRoot],
15+
testMatch: ['<rootDir>/specs/**/*.spec.ts'],
816
transform: {
917
'^.+\\.(ts|tsx|js|mjs)$': 'babel-jest',
1018
},
1119
transformIgnorePatterns: [],
12-
testMatch: ['**/node_modules/@lit-protocol/e2e/dist/specs/**/*.spec.(ts|js)'],
1320
moduleFileExtensions: ['ts', 'tsx', 'js', 'mjs', 'cjs', 'json'],
21+
testPathIgnorePatterns: [],
22+
moduleNameMapper: {
23+
'^@lit-protocol/e2e/(.*)$': path.join(srcDir, '$1'),
24+
'^@lit-protocol/e2e$': path.join(srcDir, 'index.js'),
25+
},
1426
};
15-
16-
module.exports = config;

0 commit comments

Comments
 (0)