Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/test/monaco/monaco.test.ts
3520 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as playwright from '@playwright/test';
7
import { assert } from 'chai';
8
import { injectAxe } from 'axe-playwright';
9
10
const PORT = 8563;
11
const TIMEOUT = 20 * 1000;
12
13
const APP = `http://127.0.0.1:${PORT}/dist/core.html`;
14
15
let browser: playwright.Browser;
16
let page: playwright.Page;
17
18
type BrowserType = 'chromium' | 'firefox' | 'webkit';
19
20
const browserType: BrowserType = process.env.BROWSER as BrowserType || 'chromium';
21
22
before(async function () {
23
this.timeout(TIMEOUT);
24
console.log(`Starting browser: ${browserType}`);
25
browser = await playwright[browserType].launch({
26
headless: process.argv.includes('--headless'),
27
});
28
});
29
30
after(async function () {
31
this.timeout(TIMEOUT);
32
await browser.close();
33
});
34
35
const pageErrors: any[] = [];
36
beforeEach(async function () {
37
this.timeout(TIMEOUT);
38
page = await browser.newPage({
39
viewport: {
40
width: 800,
41
height: 600
42
}
43
});
44
45
pageErrors.length = 0;
46
page.on('pageerror', (e) => {
47
console.log(e);
48
pageErrors.push(e);
49
});
50
page.on('pageerror', (e) => {
51
console.log(e);
52
pageErrors.push(e);
53
});
54
});
55
56
afterEach(async () => {
57
await page.close();
58
for (const e of pageErrors) {
59
throw e;
60
}
61
});
62
63
describe('API Integration Tests', function (): void {
64
this.timeout(TIMEOUT);
65
66
beforeEach(async () => {
67
await page.goto(APP);
68
});
69
70
it('`monaco` is not exposed as global', async function (): Promise<any> {
71
assert.strictEqual(await page.evaluate(`typeof monaco`), 'undefined');
72
});
73
74
it('Focus and Type', async function (): Promise<any> {
75
await page.evaluate(`
76
(function () {
77
instance.focus();
78
instance.trigger('keyboard', 'cursorHome');
79
instance.trigger('keyboard', 'type', {
80
text: 'a'
81
});
82
})()
83
`);
84
assert.strictEqual(await page.evaluate(`instance.getModel().getLineContent(1)`), 'afrom banana import *');
85
});
86
87
it('Type and Undo', async function (): Promise<any> {
88
await page.evaluate(`
89
(function () {
90
instance.focus();
91
instance.trigger('keyboard', 'cursorHome');
92
instance.trigger('keyboard', 'type', {
93
text: 'a'
94
});
95
instance.getModel().undo();
96
})()
97
`);
98
assert.strictEqual(await page.evaluate(`instance.getModel().getLineContent(1)`), 'from banana import *');
99
});
100
101
it('Multi Cursor', async function (): Promise<any> {
102
await page.evaluate(`
103
(function () {
104
instance.focus();
105
instance.trigger('keyboard', 'editor.action.insertCursorBelow');
106
instance.trigger('keyboard', 'editor.action.insertCursorBelow');
107
instance.trigger('keyboard', 'editor.action.insertCursorBelow');
108
instance.trigger('keyboard', 'editor.action.insertCursorBelow');
109
instance.trigger('keyboard', 'editor.action.insertCursorBelow');
110
instance.trigger('keyboard', 'type', {
111
text: '# '
112
});
113
instance.focus();
114
})()
115
`);
116
117
await page.waitForTimeout(1000);
118
119
assert.deepStrictEqual(await page.evaluate(`
120
[
121
instance.getModel().getLineContent(1),
122
instance.getModel().getLineContent(2),
123
instance.getModel().getLineContent(3),
124
instance.getModel().getLineContent(4),
125
instance.getModel().getLineContent(5),
126
instance.getModel().getLineContent(6),
127
instance.getModel().getLineContent(7),
128
]
129
`), [
130
'# from banana import *',
131
'# ',
132
'# class Monkey:',
133
'# # Bananas the monkey can eat.',
134
'# capacity = 10',
135
'# def eat(self, N):',
136
'\t\t\'\'\'Make the monkey eat N bananas!\'\'\''
137
]);
138
});
139
describe('Accessibility', function (): void {
140
beforeEach(async () => {
141
await page.goto(APP);
142
await injectAxe(page);
143
await page.evaluate(`
144
(function () {
145
instance.focus();
146
instance.trigger('keyboard', 'cursorHome');
147
instance.trigger('keyboard', 'type', {
148
text: 'a'
149
});
150
})()
151
`);
152
});
153
154
it('Editor should not have critical accessibility violations', async () => {
155
let violationCount = 0;
156
const checkedElements = new Set<string>();
157
158
// Run axe and get all results (passes and violations)
159
const axeResults = await page.evaluate(() => {
160
return window.axe.run(document, {
161
runOnly: {
162
type: 'tag',
163
values: [
164
'wcag2a',
165
'wcag2aa',
166
'wcag21a',
167
'wcag21aa',
168
'best-practice'
169
]
170
}
171
});
172
});
173
174
axeResults.violations.forEach((v: any) => {
175
const isCritical = v.impact === 'critical';
176
const emoji = isCritical ? '❌' : undefined;
177
v.nodes.forEach((node: any) => {
178
const selector = node.target?.join(' ');
179
if (selector && emoji) {
180
checkedElements.add(selector);
181
console.log(`${emoji} FAIL: ${selector} - ${v.id} - ${v.description}`);
182
}
183
});
184
violationCount += isCritical ? 1 : 0;
185
});
186
187
axeResults.passes.forEach((pass: any) => {
188
pass.nodes.forEach((node: any) => {
189
const selector = node.target?.join(' ');
190
if (selector && !checkedElements.has(selector)) {
191
checkedElements.add(selector);
192
}
193
});
194
});
195
196
playwright.expect(violationCount).toBe(0);
197
});
198
199
it('Editor should not have color contrast accessibility violations', async () => {
200
let violationCount = 0;
201
const checkedElements = new Set<string>();
202
203
const axeResults = await page.evaluate(() => {
204
return window.axe.run(document, {
205
runOnly: {
206
type: 'rule',
207
values: ['color-contrast']
208
}
209
});
210
});
211
212
axeResults.violations.forEach((v: any) => {
213
const isCritical = v.impact === 'critical';
214
const emoji = isCritical ? '❌' : undefined;
215
v.nodes.forEach((node: any) => {
216
const selector = node.target?.join(' ');
217
if (selector && emoji) {
218
checkedElements.add(selector);
219
console.log(`${emoji} FAIL: ${selector} - ${v.id} - ${v.description}`);
220
}
221
});
222
violationCount += 1;
223
});
224
225
axeResults.passes.forEach((pass: any) => {
226
pass.nodes.forEach((node: any) => {
227
const selector = node.target?.join(' ');
228
if (selector && !checkedElements.has(selector)) {
229
checkedElements.add(selector);
230
}
231
});
232
});
233
234
playwright.expect(violationCount).toBe(0);
235
});
236
it('Monaco editor container should have an ARIA role', async () => {
237
const role = await page.evaluate(() => {
238
const container = document.querySelector('.monaco-editor');
239
return container?.getAttribute('role');
240
});
241
assert.isDefined(role, 'Monaco editor container should have a role attribute');
242
});
243
244
it('Monaco editor should have an ARIA label', async () => {
245
const ariaLabel = await page.evaluate(() => {
246
const container = document.querySelector('.monaco-editor');
247
return container?.getAttribute('aria-label');
248
});
249
assert.isDefined(ariaLabel, 'Monaco editor container should have an aria-label attribute');
250
});
251
252
it('All toolbar buttons should have accessible names', async () => {
253
const buttonsWithoutLabel = await page.evaluate(() => {
254
return Array.from(document.querySelectorAll('button')).filter(btn => {
255
const label = btn.getAttribute('aria-label') || btn.textContent?.trim();
256
return !label;
257
}).map(btn => btn.outerHTML);
258
});
259
assert.deepEqual(buttonsWithoutLabel, [], 'All toolbar buttons should have accessible names');
260
});
261
});
262
});
263
264