Skip to content

Commit 6a3f395

Browse files
committed
test: add the basic E2E tests using WebdriverIO
- mocha - jasmine - cucumber
1 parent fb82fab commit 6a3f395

31 files changed

Lines changed: 3713 additions & 86 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ node_modules
66
*.vsix
77
coverage
88
e2e/logs
9+
samples/**/logs
910
**/.turbo

.vscode/tasks.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tasks": [
66
{
77
"label": "watch",
8-
"dependsOn": ["npm: watch:esbuild"],
8+
"dependsOn": ["npm: watch:build"],
99
"presentation": {
1010
"reveal": "never"
1111
},
@@ -16,23 +16,23 @@
1616
},
1717
{
1818
"type": "npm",
19-
"script": "watch:esbuild",
19+
"script": "watch:build",
2020
"group": "build",
2121
"problemMatcher": ["$esbuild-watch", "$tsc"],
2222
"isBackground": true,
23-
"label": "npm: watch:esbuild",
23+
"label": "npm: watch:build",
2424
"presentation": {
2525
"group": "watch",
2626
"reveal": "never"
2727
}
2828
},
2929
{
3030
"type": "npm",
31-
"script": "watch:tsc",
31+
"script": "watch:typecheck",
3232
"group": "build",
3333
"problemMatcher": "$tsc-watch",
3434
"isBackground": true,
35-
"label": "npm: watch:tsc",
35+
"label": "npm: watch:typecheck",
3636
"presentation": {
3737
"group": "watch",
3838
"reveal": "never"

e2e/assertions/index.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { MatcherContext } from 'expect'
2+
import type { TreeItem } from 'wdio-vscode-service'
3+
import type { STATUS } from '../helpers.js'
4+
5+
export interface ExpectedTreeItem {
6+
label: string
7+
status: StatusStrings
8+
children?: ExpectedTreeItem[]
9+
}
10+
11+
export type StatusStrings = (typeof STATUS)[keyof typeof STATUS]
12+
13+
class MismatchTreeStructureError extends Error {
14+
constructor(public assertionMessage: () => string) {
15+
super('Mismatch')
16+
}
17+
}
18+
19+
async function expectTreeToMatchStructure(
20+
this: MatcherContext,
21+
receivedTree: TreeItem[],
22+
expectedTree: ExpectedTreeItem[],
23+
level = 0,
24+
index = 0
25+
) {
26+
try {
27+
if (expectedTree.length !== receivedTree.length) {
28+
throw new MismatchTreeStructureError(
29+
() =>
30+
`Mismatch the number of items at (${index} : ${level})` +
31+
` Expected: ${this.utils.printExpected(expectedTree.length)}` +
32+
` Received: ${this.utils.printReceived(receivedTree.length)}`
33+
)
34+
}
35+
36+
for (const [index, item] of Object.entries(receivedTree)) {
37+
const rootLabel = await item.getLabel()
38+
39+
const expectItem = expectedTree[Number(index)]
40+
const labelRegex = new RegExp(expectItem.label)
41+
if (!labelRegex.test(rootLabel)) {
42+
throw new MismatchTreeStructureError(
43+
() =>
44+
`Mismatch the label of items at (${index} : ${level})` +
45+
` Expected: ${this.utils.printExpected(expectItem.label)}` +
46+
` Received: ${this.utils.printReceived(rootLabel)}`
47+
)
48+
}
49+
const receivedStatus = rootLabel.match(/\(([^)]+)\)/)
50+
if (!receivedStatus || receivedStatus[1] !== expectItem.status) {
51+
throw new MismatchTreeStructureError(
52+
() =>
53+
`Mismatch the status of items at (${index} : ${level})` +
54+
` Expected: ${this.utils.printExpected(expectItem.status)}` +
55+
` Received: ${this.utils.printReceived(receivedStatus![1])}` +
56+
` (${rootLabel})`
57+
)
58+
}
59+
60+
if (!(await item.isExpanded())) {
61+
await item.expand()
62+
}
63+
64+
const children = await item.getChildren()
65+
const expectedChildren = expectItem.children
66+
if (children && expectedChildren) {
67+
await expectTreeToMatchStructure.call(this, children, expectedChildren, level + 1, Number(index))
68+
}
69+
}
70+
return {
71+
pass: true,
72+
message: () => 'All ok.',
73+
}
74+
} catch (error) {
75+
if (level > 0) {
76+
throw error
77+
}
78+
const message = error instanceof MismatchTreeStructureError ? error.assertionMessage : () => String(error)
79+
return {
80+
pass: false,
81+
message,
82+
}
83+
}
84+
}
85+
86+
try {
87+
if (typeof expect !== 'undefined' && expect.extend) {
88+
expect.extend({
89+
async toMatchTreeStructure(tree: TreeItem[], expectedStructure: ExpectedTreeItem[]) {
90+
return await expectTreeToMatchStructure.call(this as unknown as MatcherContext, tree, expectedStructure)
91+
},
92+
})
93+
}
94+
} catch (error) {
95+
console.warn('Failed to extend expect:', error)
96+
}

e2e/assertions/wdio.shim.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'expect-webdriverio'
2+
3+
declare global {
4+
namespace ExpectWebdriverIO {
5+
interface Matchers<R, T> {
6+
toBeWithinRange(floor: number, ceiling: number): R
7+
toMatchTreeStructure(expectedStructure: ExpectedTreeItem[]): R
8+
}
9+
}
10+
}

e2e/helpers.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { DefaultTreeSection } from 'wdio-vscode-service'
2+
import type { StatusStrings } from 'assertions/index.ts'
3+
import type { TreeItem, Workbench, ViewControl, ViewContent } from 'wdio-vscode-service'
4+
5+
export const STATUS = {
6+
NOT_YET_RUN: 'Not yet run',
7+
PASSED: 'Passed',
8+
FAILED: 'Failed',
9+
SKIPPED: 'Skipped',
10+
} as const
11+
12+
export async function openTestingView(workbench: Workbench) {
13+
const activityBar = workbench.getActivityBar()
14+
let testingViewControl: ViewControl | undefined
15+
await browser.waitUntil(
16+
async () => {
17+
testingViewControl = await activityBar.getViewControl('Testing')
18+
return typeof testingViewControl !== 'undefined'
19+
},
20+
{
21+
timeoutMsg: 'Testing view was not opened',
22+
}
23+
)
24+
await testingViewControl!.openView()
25+
return testingViewControl!
26+
}
27+
28+
export async function getTestingSection(content: ViewContent) {
29+
return new DefaultTreeSection(content.locatorMap, content.section$, content)
30+
}
31+
32+
export async function waitForResolved(browser: WebdriverIO.Browser, item: TreeItem) {
33+
await browser.waitUntil(
34+
async () => {
35+
const regex = new RegExp('Resolving WebdriverIO Tests')
36+
return !regex.test(await item.getLabel())
37+
},
38+
{
39+
timeoutMsg: 'Resolving tests ware not finished',
40+
}
41+
)
42+
}
43+
44+
export async function waitForTestStatus(browser: WebdriverIO.Browser, item: TreeItem, status: StatusStrings) {
45+
await browser.waitUntil(
46+
async () => {
47+
const label = await item.getLabel()
48+
// wdio.conf.ts (Passed), in 3.2s
49+
const matcherResult = label.match(/\(([^)]+)\), in .*s$/)
50+
return matcherResult && matcherResult[1] === status
51+
},
52+
{
53+
timeout: 180000,
54+
timeoutMsg: `expected status(${status}) to be different(current: ${await item.getLabel()})`,
55+
}
56+
)
57+
}
58+
59+
export async function clearAllTestResults(workbench: Workbench) {
60+
const bottomBarPanel = workbench.getBottomBar()
61+
const tabTitle = 'Test Results'
62+
const actionTitle = 'Clear All Results'
63+
try {
64+
await bottomBarPanel.toggle(true)
65+
const tabContainer = await bottomBarPanel.tabContainer$
66+
const tab = (await tabContainer.$(`.//a[starts-with(@aria-label, '${tabTitle}')]`)) as WebdriverIO.Element
67+
if (await tab.isExisting()) {
68+
await tab.click()
69+
await ((await bottomBarPanel.actions$) as WebdriverIO.Element)
70+
.$(`.//a[@aria-label='${actionTitle}']`)
71+
.click()
72+
}
73+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
74+
} catch (_error) {
75+
// pass
76+
}
77+
}

e2e/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@vscode-wdio/e2e",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"exports": {
7+
".": {
8+
"types": "./dist/index.d.ts",
9+
"import": "./dist/index.js"
10+
}
11+
},
12+
"scripts": {
13+
"test:e2e": "npm-run-all test:e2e:*",
14+
"test:e2e:mocha": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=mocha xvfb-maybe pnpm run wdio",
15+
"test:e2e:jasmine": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=jasmine xvfb-maybe pnpm run wdio",
16+
"test:e2e:cucumber": "cross-env VSCODE_WDIO_E2E_FRAMEWORK=cucumber xvfb-maybe pnpm run wdio",
17+
"wdio": "wdio run ./wdio.conf.ts"
18+
},
19+
"devDependencies": {
20+
"@wdio/cli": "^9.13.0",
21+
"@wdio/globals": "^9.12.6",
22+
"@wdio/local-runner": "^9.13.0",
23+
"@wdio/mocha-framework": "^9.13.0",
24+
"@wdio/spec-reporter": "^9.13.0",
25+
"chai": "^5.2.0",
26+
"expect": "^29.7.0",
27+
"wdio-vscode-service": "^6.1.3",
28+
"webdriver": "^9.13.0",
29+
"webdriverio": "^9.13.0"
30+
}
31+
}

0 commit comments

Comments
 (0)