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 ADDED
@@ -0,0 +1,11 @@
1
+ # create-vscode-ui-test
2
+
3
+ Scaffold Workbench UI tests for VS Code extensions.
4
+
5
+ ```bash
6
+ npx create-vedats@latest
7
+ ```
8
+
9
+ Flags:
10
+
11
+ - `--target <path>`
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
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }