create-vedats 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/package.json +21 -0
- package/src/index.ts +119 -0
- package/templates/.github/workflows/ui-tests.yml +53 -0
- package/templates/tests/ui/README.md +20 -0
- package/templates/tests/ui/dsl/workbench.ts +1 -0
- package/templates/tests/ui/specs/ui.e2e.ts +1 -0
- package/templates/tests/ui/specs/workbench.e2e.ts +13 -0
- package/templates/tests/ui/tsconfig.json +18 -0
- package/templates/tests/ui/wdio.conf.ts +76 -0
- package/tsconfig.json +12 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-vedats",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold Workbench UI tests for VS Code extensions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/futouyiba/vedats.git"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-vedats": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.11.0",
|
|
19
|
+
"typescript": "^5.3.3"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
type PackageJson = {
|
|
7
|
+
scripts?: Record<string, string>;
|
|
8
|
+
devDependencies?: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DEV_DEPS: Record<string, string> = {
|
|
12
|
+
'@wdio/cli': '^8.32.2',
|
|
13
|
+
'@wdio/globals': '^8.32.2',
|
|
14
|
+
'@wdio/local-runner': '^8.32.2',
|
|
15
|
+
'@wdio/mocha-framework': '^8.32.2',
|
|
16
|
+
'@wdio/spec-reporter': '^8.32.2',
|
|
17
|
+
'@wdio/types': '^8.32.2',
|
|
18
|
+
'expect-webdriverio': '^4.12.4',
|
|
19
|
+
'ts-node': '^10.9.2',
|
|
20
|
+
'vedats-harness': '^0.1.0',
|
|
21
|
+
'wdio-vscode-service': '^6.1.4',
|
|
22
|
+
'webdriverio': '^8.32.2'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function parseArgs(argv: string[]): { targetDir: string } {
|
|
26
|
+
const targetFlagIndex = argv.findIndex((arg) => arg === '--target');
|
|
27
|
+
if (targetFlagIndex !== -1 && argv[targetFlagIndex + 1]) {
|
|
28
|
+
return { targetDir: path.resolve(argv[targetFlagIndex + 1]) };
|
|
29
|
+
}
|
|
30
|
+
return { targetDir: process.cwd() };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ensureFile(filePath: string): void {
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
throw new Error(`Required file not found: ${filePath}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function copyTemplate(targetDir: string): void {
|
|
40
|
+
const templateDir = path.resolve(__dirname, '..', 'templates');
|
|
41
|
+
if (!fs.existsSync(templateDir)) {
|
|
42
|
+
throw new Error(`Template directory missing: ${templateDir}`);
|
|
43
|
+
}
|
|
44
|
+
fs.cpSync(templateDir, targetDir, { recursive: true, errorOnExist: false });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function updatePackageJson(targetDir: string): void {
|
|
48
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
49
|
+
ensureFile(packageJsonPath);
|
|
50
|
+
|
|
51
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf8');
|
|
52
|
+
const pkg = JSON.parse(raw) as PackageJson;
|
|
53
|
+
|
|
54
|
+
pkg.scripts ??= {};
|
|
55
|
+
pkg.devDependencies ??= {};
|
|
56
|
+
|
|
57
|
+
pkg.scripts['test:ui'] ??= 'wdio run ./tests/ui/wdio.conf.ts';
|
|
58
|
+
pkg.scripts['test:ui:headed'] ??= 'wdio run ./tests/ui/wdio.conf.ts';
|
|
59
|
+
|
|
60
|
+
if (pkg.scripts.compile && !pkg.scripts['pretest:ui']) {
|
|
61
|
+
pkg.scripts['pretest:ui'] = 'npm run compile';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const [dep, version] of Object.entries(DEFAULT_DEV_DEPS)) {
|
|
65
|
+
if (!pkg.devDependencies[dep]) {
|
|
66
|
+
pkg.devDependencies[dep] = version;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function updateTsconfig(targetDir: string): void {
|
|
74
|
+
const tsconfigPath = path.join(targetDir, 'tsconfig.json');
|
|
75
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const raw = fs.readFileSync(tsconfigPath, 'utf8');
|
|
80
|
+
const config = JSON.parse(raw) as { exclude?: string[] };
|
|
81
|
+
const exclude = new Set(config.exclude ?? []);
|
|
82
|
+
exclude.add('tests');
|
|
83
|
+
config.exclude = Array.from(exclude);
|
|
84
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function updateGitignore(targetDir: string): void {
|
|
88
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
89
|
+
const entry = '**/artifacts/vscode-ui/';
|
|
90
|
+
|
|
91
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
92
|
+
fs.writeFileSync(gitignorePath, `${entry}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
97
|
+
if (!content.includes(entry)) {
|
|
98
|
+
const updated = `${content.trimEnd()}
|
|
99
|
+
|
|
100
|
+
${entry}
|
|
101
|
+
fs.writeFileSync(gitignorePath, updated, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function main(): void {
|
|
106
|
+
const { targetDir } = parseArgs(process.argv);
|
|
107
|
+
try {
|
|
108
|
+
copyTemplate(targetDir);
|
|
109
|
+
updatePackageJson(targetDir);
|
|
110
|
+
updateTsconfig(targetDir);
|
|
111
|
+
updateGitignore(targetDir);
|
|
112
|
+
process.stdout.write(`UI test scaffold added to ${targetDir}\n`);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
main();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: UI Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
ui-tests:
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: 20
|
|
24
|
+
cache: npm
|
|
25
|
+
cache-dependency-path: package-lock.json
|
|
26
|
+
|
|
27
|
+
- name: Cache VS Code downloads
|
|
28
|
+
uses: actions/cache@v4
|
|
29
|
+
with:
|
|
30
|
+
path: .vscode-test
|
|
31
|
+
key: ${{ runner.os }}-vscode-${{ hashFiles('package-lock.json') }}
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: npm ci
|
|
35
|
+
|
|
36
|
+
- name: Run UI tests (Linux)
|
|
37
|
+
if: runner.os == 'Linux'
|
|
38
|
+
run: xvfb-run -a npm run test:ui
|
|
39
|
+
|
|
40
|
+
- name: Run UI tests (Windows)
|
|
41
|
+
if: runner.os == 'Windows'
|
|
42
|
+
run: npm run test:ui
|
|
43
|
+
|
|
44
|
+
- name: Run UI tests (macOS)
|
|
45
|
+
if: runner.os == 'macOS'
|
|
46
|
+
run: npm run test:ui
|
|
47
|
+
|
|
48
|
+
- name: Upload UI artifacts
|
|
49
|
+
if: always()
|
|
50
|
+
uses: actions/upload-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: vscode-ui-artifacts-${{ runner.os }}
|
|
53
|
+
path: artifacts/vscode-ui
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# VS Code UI Tests (Workbench)
|
|
2
|
+
|
|
3
|
+
Run:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm run test:ui
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Artifacts:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
artifacts/vscode-ui/<runId>/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Required contents include:
|
|
16
|
+
|
|
17
|
+
- `steps.json`
|
|
18
|
+
- `screenshots/`
|
|
19
|
+
- `logs/`
|
|
20
|
+
- `ui-dump/diagnostics.json`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'vedats-harness';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './workbench.e2e';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expect, browser } from '@wdio/globals';
|
|
2
|
+
import { waitForNotification } from '../dsl/workbench';
|
|
3
|
+
|
|
4
|
+
describe('Workbench UI smoke test', () => {
|
|
5
|
+
it('shows a VS Code notification', async () => {
|
|
6
|
+
await browser.executeWorkbench((vscode) => {
|
|
7
|
+
vscode.window.showInformationMessage('UI test ready');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
await waitForNotification('UI test ready');
|
|
11
|
+
expect(true).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"types": [
|
|
7
|
+
"node",
|
|
8
|
+
"webdriverio/async",
|
|
9
|
+
"@wdio/mocha-framework",
|
|
10
|
+
"expect-webdriverio",
|
|
11
|
+
"wdio-vscode-service"
|
|
12
|
+
],
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strict": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["./**/*.ts"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import type { Options } from '@wdio/types';
|
|
3
|
+
import { initRunContext, captureFailureArtifacts } from 'vedats-harness';
|
|
4
|
+
|
|
5
|
+
const rootDir = path.resolve(__dirname, '..', '..');
|
|
6
|
+
const runContext = initRunContext(rootDir);
|
|
7
|
+
|
|
8
|
+
const vscodeVersion = process.env.VSCODE_UI_VERSION ?? 'stable';
|
|
9
|
+
const logLevel = (process.env.WDIO_LOG_LEVEL ?? 'info') as Options.Testrunner['logLevel'];
|
|
10
|
+
|
|
11
|
+
export const config: Options.Testrunner = {
|
|
12
|
+
runner: 'local',
|
|
13
|
+
specs: [path.join(__dirname, 'specs', 'ui.e2e.ts')],
|
|
14
|
+
maxInstances: 1,
|
|
15
|
+
maxInstancesPerCapability: 1,
|
|
16
|
+
logLevel,
|
|
17
|
+
outputDir: runContext.logsDir,
|
|
18
|
+
framework: 'mocha',
|
|
19
|
+
reporters: ['spec'],
|
|
20
|
+
mochaOpts: {
|
|
21
|
+
timeout: 120000
|
|
22
|
+
},
|
|
23
|
+
autoCompileOpts: {
|
|
24
|
+
autoCompile: true,
|
|
25
|
+
tsNodeOpts: {
|
|
26
|
+
project: path.join(__dirname, 'tsconfig.json'),
|
|
27
|
+
transpileOnly: true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
services: [
|
|
31
|
+
['vscode', { cachePath: path.join(rootDir, '.vscode-test') }]
|
|
32
|
+
],
|
|
33
|
+
capabilities: [
|
|
34
|
+
{
|
|
35
|
+
maxInstances: 1,
|
|
36
|
+
browserName: 'vscode',
|
|
37
|
+
browserVersion: vscodeVersion,
|
|
38
|
+
'wdio:vscodeOptions': {
|
|
39
|
+
extensionPath: rootDir,
|
|
40
|
+
workspacePath: rootDir,
|
|
41
|
+
storagePath: runContext.storageDir,
|
|
42
|
+
verboseLogging: true,
|
|
43
|
+
userSettings: {
|
|
44
|
+
'extensions.autoUpdate': false,
|
|
45
|
+
'extensions.ignoreRecommendations': true,
|
|
46
|
+
'security.workspace.trust.enabled': false,
|
|
47
|
+
'security.workspace.trust.startupPrompt': 'never',
|
|
48
|
+
'telemetry.telemetryLevel': 'off',
|
|
49
|
+
'update.mode': 'none',
|
|
50
|
+
'workbench.enableExperiments': false,
|
|
51
|
+
'workbench.startupEditor': 'none',
|
|
52
|
+
'workbench.tips.enabled': false,
|
|
53
|
+
'workbench.welcomePage.walkthroughs.openOnInstall': false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
beforeSession: (_config, capabilities, _specs, cid) => {
|
|
59
|
+
const rawCapabilities = capabilities as Record<string, unknown>;
|
|
60
|
+
const options = rawCapabilities['wdio:vscodeOptions'];
|
|
61
|
+
if (options && typeof options === 'object') {
|
|
62
|
+
(options as Record<string, unknown>).storagePath = path.join(
|
|
63
|
+
runContext.storageDir,
|
|
64
|
+
cid.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
before: async () => {
|
|
69
|
+
process.env.VSCODE_UI_RUN_ID = runContext.runId;
|
|
70
|
+
},
|
|
71
|
+
afterTest: async (_test, _context, result) => {
|
|
72
|
+
if (!result.passed) {
|
|
73
|
+
await captureFailureArtifacts(result.error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|