Skip to content

Commit 5244353

Browse files
authored
Refactor how files are opened in UI Tests (microsoft#7678)
* Better way of opening documents
1 parent 1fe697a commit 5244353

10 files changed

Lines changed: 116 additions & 96 deletions

File tree

uitests/bootstrap/extension/extension.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,23 @@ function activate(context) {
116116
await vscode.window.activeTerminal.sendText(command, true);
117117
fs.unlinkSync(filePath);
118118
});
119+
vscode.commands.registerCommand('smoketest.openFile', async () => {
120+
const filePath = path.join(__dirname, '..', 'openFile.txt');
121+
try {
122+
fs.unlinkSync(path.join(__dirname, '..', 'openFile_error.txt'), util.format(ex));
123+
} catch {}
124+
try {
125+
const fileToOpen = fs
126+
.readFileSync(filePath)
127+
.toString()
128+
.trim();
129+
const doc = await vscode.workspace.openTextDocument(fileToOpen);
130+
await vscode.window.showTextDocument(doc);
131+
fs.unlinkSync(filePath);
132+
} catch (ex) {
133+
fs.writeFileSync(path.join(__dirname, '..', 'openFile_error.txt'), util.format(ex));
134+
}
135+
});
119136
vscode.commands.registerCommand('smoketest.updateSettings', async () => {
120137
const filePath = path.join(__dirname, '..', 'settingsToUpdate.txt');
121138
try {

uitests/bootstrap/extension/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
"command": "smoketest.stopDebuggingPython",
3535
"title": "Stop Debugging Python"
3636
},
37+
{
38+
"command": "smoketest.openFile",
39+
"title": "Smoke: Open File"
40+
},
3741
{
3842
"command": "smoketest.runInTerminal",
3943
"title": "Smoke: Run Command In Terminal"

uitests/features/interpreter/terminal.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Feature: Terminal
3030
open('log.log', 'w').write('Hello World')
3131
"""
3232
And a file named "hello word/log.log" does not exist
33-
When I open the file "run in terminal.py"
33+
When I open the file "hello word/run in terminal.py"
3434
And I select the command "Python: Run Python File in Terminal"
3535
# Wait for some time, as it could take a while for terminal to get activated.
3636
# Slow on windows.

uitests/features/testing/explorer/debug.feature

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ Feature: Test Explorer (debugging)
5656
Then the test explorer icon will be visible
5757
When I select the command "View: Show Test"
5858
And I expand all of the nodes in the test explorer
59-
When I add a breakpoint to line 33 in "test_one.py"
60-
And I add a breakpoint to line 23 in "test_one.py"
59+
When I add a breakpoint to line 33 in "tests/test_one.py"
60+
And I add a breakpoint to line 23 in "tests/test_one.py"
6161
And I debug the node "test_three_first_suite" from the test explorer
6262
Then the debugger starts
6363
And the debugger pauses
64-
And the current stack frame is at line 33 in "test_one.py"
64+
And the current stack frame is at line 33 in "tests/test_one.py"
6565
When I select the command "Debug: Continue"
6666
Then the debugger stops
6767

@@ -79,19 +79,19 @@ Feature: Test Explorer (debugging)
7979
Then the test explorer icon will be visible
8080
When I select the command "View: Show Test"
8181
And I expand all of the nodes in the test explorer
82-
When I add a breakpoint to line 33 in "test_one.py"
83-
And I add a breakpoint to line 28 in "test_one.py"
84-
And I add a breakpoint to line 23 in "test_one.py"
82+
When I add a breakpoint to line 33 in "tests/test_one.py"
83+
And I add a breakpoint to line 28 in "tests/test_one.py"
84+
And I add a breakpoint to line 23 in "tests/test_one.py"
8585
And I debug the node "TestFirstSuite" from the test explorer
8686
Then the debugger starts
8787
And the debugger pauses
88-
And the current stack frame is at line 23 in "test_one.py"
88+
And the current stack frame is at line 23 in "tests/test_one.py"
8989
When I select the command "Debug: Continue"
9090
Then the debugger pauses
91-
And the current stack frame is at line 33 in "test_one.py"
91+
And the current stack frame is at line 33 in "tests/test_one.py"
9292
When I select the command "Debug: Continue"
9393
Then the debugger pauses
94-
And the current stack frame is at line 28 in "test_one.py"
94+
And the current stack frame is at line 28 in "tests/test_one.py"
9595
When I select the command "Debug: Continue"
9696
Then the debugger stops
9797

@@ -110,9 +110,9 @@ Feature: Test Explorer (debugging)
110110
Then the test explorer icon will be visible
111111
When I select the command "View: Show Test"
112112
And I expand all of the nodes in the test explorer
113-
When I add a breakpoint to line 23 in "test_one.py"
114-
And I add a breakpoint to line 38 in "test_one.py"
115-
And I add a breakpoint to line 23 in "test_two.py"
113+
When I add a breakpoint to line 23 in "tests/test_one.py"
114+
And I add a breakpoint to line 38 in "tests/test_one.py"
115+
And I add a breakpoint to line 23 in "tests/test_two.py"
116116
And I select the command "Python: Debug All Tests"
117117
Then the debugger starts
118118
And the debugger pauses

uitests/src/steps/documents.ts

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,16 @@
77

88
import * as assert from 'assert';
99
import { expect } from 'chai';
10-
import { Given, Then, When } from 'cucumber';
10+
import { Given, Then, When, World } from 'cucumber';
1111
import * as fs from 'fs-extra';
1212
import * as path from 'path';
1313
import { CucumberRetryMax10Seconds, CucumberRetryMax5Seconds } from '../constants';
1414
import { noop, retryWrapper, sleep } from '../helpers';
15-
import { warn } from '../helpers/logger';
1615
import { IApplication } from '../types';
1716

1817
// tslint:disable-next-line: no-var-requires no-require-imports
1918
const clipboardy = require('clipboardy');
2019

21-
// const autoCompletionListItemSlector = '.editor-widget.suggest-widget.visible .monaco-list-row a.label-name .monaco-highlighted-label';
22-
2320
When('I create a new file', async function() {
2421
await this.app.documents.createNewUntitledFile();
2522
});
@@ -36,29 +33,6 @@ When('I create a new file with the following content', async function(contents:
3633
await sleep(200);
3734
});
3835

39-
Given('a file named {string} is created with the following content', async function(filename: string, contents: string) {
40-
const fullpath = path.join(this.app.workspacePathOrFolder, filename);
41-
await fs.ensureDir(path.dirname(fullpath));
42-
await fs.writeFile(fullpath, contents);
43-
// Ensure VS Code has had time to refresh to explorer and is aware of the file.
44-
// Else if we later attempt to open this file, VSC might not be aware of it and woudn't display anything in the `quick open` dropdown.
45-
const openRecentlyCreatedDocument = async () => {
46-
await this.app.documents.refreshExplorer();
47-
// Sometimes VS Code just doesn't know about files created from outside VS Code.
48-
// Not unless we expand the file explorer.
49-
// Hopefully we don't have (write) tests where files are created in nested folders and not detected by VSC, but required to be opened.
50-
const opened = await this.app.quickopen
51-
.openFile(path.basename(filename))
52-
.then(() => true)
53-
.catch(ex => warn(`Failed to open the file '${filename}' in VS Code, but continuing (hopefully file will not have to be opened)`, ex));
54-
if (opened === true) {
55-
await this.app.quickopen.runCommand('View: Close Editor');
56-
}
57-
};
58-
59-
await retryWrapper({ timeout: 5000 }, openRecentlyCreatedDocument);
60-
});
61-
6236
When('I change the language of the file to {string}', async function(language: string) {
6337
await this.app.quickopen.runCommand('Change Language Mode');
6438
await this.app.quickinput.select({ value: language });
@@ -103,19 +77,19 @@ Then('the file {string} is opened', async function(file: string) {
10377
await this.app.documents.waitUntilFileOpened(file);
10478
});
10579

106-
// Then('a file named {string} is created with the following content', async (fileName: string, contents: string) => {
107-
// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName);
108-
// await fs.mkdirp(path.dirname(fullFilePath)).catch(noop);
109-
// await fs.writeFile(fullFilePath, contents);
110-
// await sleep(1000);
111-
// });
80+
async function createFile(this: World, filename: string, contents: string) {
81+
const fullpath = path.join(this.app.workspacePathOrFolder, filename);
82+
await fs.ensureDir(path.dirname(fullpath));
83+
await fs.writeFile(fullpath, contents);
84+
}
11285

113-
// When('the file {string} has the following content', async (fileName: string, contents: string) => {
114-
// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName);
115-
// await fs.mkdirp(path.dirname(fullFilePath)).catch(noop);
116-
// await fs.writeFile(fullFilePath, contents);
117-
// await sleep(1000);
118-
// });
86+
Given('a file named {string} is created with the following content', async function(filename: string, contents: string) {
87+
await createFile.call(this, filename, contents);
88+
});
89+
90+
When('the file {string} has the following content', async function(filename: string, contents: string) {
91+
await createFile.call(this, filename, contents);
92+
});
11993

12094
Given('a file named {string} does not exist', async function(fileName: string) {
12195
const fullFilePath = path.join(this.app.workspacePathOrFolder, fileName);
@@ -128,12 +102,6 @@ Given('the file {string} does not exist', async function(fileName: string) {
128102
await sleep(1000);
129103
});
130104

131-
// Then('a file named {string} exists', async (fileName: string) => {
132-
// const fullFilePath = path.join(context.app.workspacePathOrFolder, fileName);
133-
// const exists = await fs.pathExists(fullFilePath);
134-
// expect(exists).to.equal(true, `File '${fullFilePath}' should exist`);
135-
// });
136-
137105
async function expectFile(app: IApplication, fileName: string, timeout = 1000) {
138106
const checkFile = async () => {
139107
const fullFilePath = path.join(app.workspacePathOrFolder, fileName);

uitests/src/steps/statusbar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { Given, Then, When } from 'cucumber';
1010
import { CucumberRetryMax5Seconds } from '../constants';
1111
import { noop } from '../helpers';
1212
import '../helpers/extensions';
13-
import { warn } from '../helpers/logger';
1413

1514
Given('the python status bar item is hidden', async function() {
1615
await this.app.statusbar.hidePythonStatusBarItem().catch(noop);
@@ -41,7 +40,8 @@ Then('a status bar item containing the text {string} is displayed', async functi
4140
Then('the status bar item containing the text {string} will be hidden within {int} seconds', async function(text: string, seconds: number) {
4241
// First, lets wait for this status bar to appear (if it doesn't appear within 3 seconds, then give up and move on).
4342
await this.app.statusbar.waitUntilStatusBarItemWithText(text, 3_000).catch(noop);
44-
await this.app.statusbar.waitUntilNoStatusBarItemWithText(text, seconds * 1_000).catch(ex => warn(`Status bar item with text '${text}' still visible.`, ex));
43+
// await this.app.statusbar.waitUntilNoStatusBarItemWithText(text, seconds * 1_000).catch(ex => warn(`Status bar item with text '${text}' still visible.`, ex));
44+
await this.app.statusbar.waitUntilNoStatusBarItemWithText(text, seconds * 1_000);
4545
});
4646

4747
// Then('the python the status bar is not visible', async () => {

uitests/src/steps/testing.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,46 @@
66
// tslint:disable: no-invalid-this
77

88
import { expect } from 'chai';
9-
import { Then, When } from 'cucumber';
9+
import { Then, When, World } from 'cucumber';
1010
import { CucumberRetryMax5Seconds } from '../constants';
11-
import { noop, sleep } from '../helpers';
11+
import { noop } from '../helpers';
1212
import { IApplication, TestExplorerNodeStatus } from '../types';
1313

1414
Then('the test explorer icon will be visible', async function() {
1515
await this.app.testExplorer.waitUntilIconVisible(5_000);
1616
});
1717

18-
// Surely tests can't take more than 30s to get discovered.
19-
When('I wait for test discovery to complete', async function() {
18+
async function waitForTestsToStopRunningOrDiscovering(this: World, type: 'running' | 'discovering') {
19+
const message = type === 'running' ? 'Running Tests' : 'Discovering Tests';
20+
2021
// Wait for tests discovery to first start.
21-
await Promise.race([
22-
// Wait either for 3 seconds (we know tests would have stated discoverying by then).
23-
sleep(3_000),
24-
// Or until we can see the discovering icon.
22+
await Promise.all([
23+
// Wait until we can see the discovering/running icon.
2524
// Note, wait for icon to be Visible only if Test Explorer is already visible.
2625
// Else this will result in displaying the test explorer and then checking the visibility of the icon.
27-
// All of that could exceed 3 seconds (even though the Promise.race would resolve the other code would continue to run).
28-
// I.e. using Promise.race should only be used when we know there are no side effects and other one can and will complete withouth any issues.
26+
// All of that could exceed 3 seconds, we don't need to wait for more than 3secs.
2927
this.app.testExplorer
3028
.isOpened()
3129
.then(visible => (visible ? this.app.testExplorer.waitUntilToolbarIconVisible('Stop', 3_000) : Promise.resolve()))
3230
.catch(noop),
33-
// Or until we can see the discovering message.
34-
this.app.statusbar.waitUntilStatusBarItemWithText('Discovering Tests', 3_000).catch(noop)
31+
// Or until we can see the discovering/running message.
32+
this.app.statusbar.waitUntilStatusBarItemWithText(message, 3_000).catch(noop)
3533
]);
3634

3735
await Promise.all([
3836
// Ensure the `stop` icon is not visible in the explorer toolbar.
3937
this.app.testExplorer.waitUntilTestsStop(30_000),
40-
// Also ensure there is no statubar item with the text 'Discovering Tests'
41-
this.app.statusbar.waitUntilNoStatusBarItemWithText('Discovering Tests', 30_000)
38+
// Also ensure there is no statubar item with the text 'Discovering Tests' or 'Running Tests'
39+
this.app.statusbar.waitUntilNoStatusBarItemWithText(message, 30_000)
4240
]);
41+
}
42+
43+
When('I wait for test discovery to complete', async function() {
44+
await waitForTestsToStopRunningOrDiscovering.call(this, 'discovering');
4345
});
4446

45-
// Surely pythonn tests (in our UI Tests) can't take more than 30s to run.
4647
When('I wait for tests to complete running', async function() {
47-
await this.app.testExplorer.waitUntilTestsStop(30_000);
48+
await waitForTestsToStopRunningOrDiscovering.call(this, 'running');
4849
});
4950

5051
Then('there are {int} nodes in the test explorer', CucumberRetryMax5Seconds, async function(expectedCount: number) {
@@ -94,7 +95,7 @@ When('I run failed tests', async function() {
9495
});
9596

9697
Then('the stop icon is not visible in the toolbar', async function() {
97-
await this.app.testExplorer.waitUntilToolbarIconVisible('Stop');
98+
await this.app.testExplorer.waitUntilToolbarIconHidden('Stop');
9899
});
99100
When('I click the test node with the label {string}', async function(label: string) {
100101
await this.app.testExplorer.clickNode(label);

uitests/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ export interface IInterpreters {
677677

678678
export type TestExplorerToolbarIcon = 'Stop' | 'RunFailedTests';
679679
export type TestingAction = 'run' | 'debug' | 'open';
680-
export type TestExplorerNodeStatus = 'Unknown' | 'Success' | 'Progress' | 'Skip' | 'Ok' | 'Pass' | 'Fail' | 'Error';
680+
export type TestExplorerNodeStatus = 'Unknown' | 'Success' | 'Progress' | 'Skip' | 'Ok' | 'Pass' | 'Fail';
681681
export interface ITestExplorer {
682682
isOpened(): Promise<boolean>;
683683
isIconVisible(): Promise<boolean>;

uitests/src/vscode/quickOpen.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
'use strict';
55

66
import { EventEmitter } from 'events';
7-
import { RetryMax10Seconds, RetryMax20Seconds, RetryMax2Seconds, RetryMax30Seconds } from '../constants';
8-
import { retry, retryWrapper } from '../helpers';
7+
import * as fs from 'fs-extra';
8+
import * as path from 'path';
9+
import { RetryMax10Seconds, RetryMax2Seconds, RetryMax30Seconds } from '../constants';
10+
import { noop, retry, sleep } from '../helpers';
911
import { debug, warn } from '../helpers/logger';
1012
import { Selector } from '../selectors';
1113
import { IApplication, IQuickOpen } from '../types';
@@ -22,20 +24,45 @@ export class QuickOpen extends EventEmitter implements IQuickOpen {
2224
super();
2325
}
2426
public async openFile(fileName: string): Promise<void> {
25-
let retryCounter = 0;
26-
const tryOpening = async () => {
27-
retryCounter += 1;
28-
// Possible VSC explorer hasn't refreshed, and it hasn't detected a new file in the file system.
29-
if (retryCounter > 1) {
30-
await this.app.documents.refreshExplorer();
27+
const fullFilePath = path.join(this.app.workspacePathOrFolder, fileName);
28+
const openFileTxt = path.join(this.app.extensionsPath, 'openFile.txt');
29+
const errorFile = path.join(this.app.extensionsPath, 'openFile_error.txt');
30+
await Promise.all([fs.remove(errorFile).catch(noop), fs.writeFile(openFileTxt, fullFilePath)]);
31+
32+
await this.app.quickopen.runCommand('Smoke: Open File');
33+
// Wait for 5 seconds for file to get opened.
34+
// If file has been deleted then yes it has been opened, else error
35+
for (const _ of [1, 2, 3, 4, 5]) {
36+
if (await fs.pathExists(openFileTxt)) {
37+
await sleep(500);
38+
continue;
3139
}
32-
// await this.runCommand('Go to File...');
33-
await this.open();
34-
await this._selectValue(fileName, fileName);
35-
await this.app.documents.waitUntilFileOpened(fileName);
36-
};
40+
return;
41+
}
42+
43+
let errorMessage = '';
44+
if (await fs.pathExists(errorFile)) {
45+
errorMessage += await fs.readFile(errorFile);
46+
}
47+
if (await fs.pathExists(openFileTxt)) {
48+
errorMessage += await fs.readFile(openFileTxt);
49+
}
50+
throw new Error(`Error opening file '${fullFilePath}'.\n ${errorMessage}`);
51+
52+
// let retryCounter = 0;
53+
// const tryOpening = async () => {
54+
// retryCounter += 1;
55+
// // Possible VSC explorer hasn't refreshed, and it hasn't detected a new file in the file system.
56+
// if (retryCounter > 1) {
57+
// await this.app.documents.refreshExplorer();
58+
// }
59+
// // await this.runCommand('Go to File...');
60+
// await this.open();
61+
// await this._selectValue(fileName, fileName);
62+
// await this.app.documents.waitUntilFileOpened(fileName);
63+
// };
3764

38-
await retryWrapper(RetryMax20Seconds, tryOpening);
65+
// await retryWrapper(RetryMax20Seconds, tryOpening);
3966
}
4067
/**
4168
* Don't know what UI element in VSC handles keyboard events.

uitests/src/vscode/testExplorer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { RetryMax10Seconds } from '../constants';
77
import { retry, sleep } from '../helpers';
88
import '../helpers/extensions';
9+
import { warn } from '../helpers/logger';
910
import { Selector } from '../selectors';
1011
import { IApplication, ITestExplorer, TestExplorerNodeStatus, TestExplorerToolbarIcon, TestingAction } from '../types';
1112

@@ -15,8 +16,7 @@ const statusToIconMapping: Map<TestExplorerNodeStatus, string> = new Map([
1516
['Ok', 'status-ok.svg'],
1617
['Pass', 'status-ok.svg'],
1718
['Success', 'status-ok.svg'],
18-
['Fail', 'status-error.svg'],
19-
['Error', 'status-error.svg']
19+
['Fail', 'status-error.svg']
2020
]);
2121
// 100ms was too low in version 1.38 of VS Code.
2222
const delayForUIToUpdate = 150;
@@ -108,11 +108,14 @@ export class TestExplorer implements ITestExplorer {
108108
}
109109
}
110110
} finally {
111+
// Assumption that if there are <=2 nodes and we try to expand the nodes, and
112+
// yet there's nothing new, then this could be a problem.
113+
// Hence log a warning message.
111114
const visibleNodes = await this.getNodeCount();
112-
if (visibleNodes === initialNodeCount) {
115+
if (visibleNodes <= 2 && visibleNodes === initialNodeCount) {
113116
// Something is wrong, try again.
114117
// tslint:disable-next-line: no-unsafe-finally
115-
throw new Error('Retry expanding nodes. First iteration did not reveal any new nodes!');
118+
warn('Retry expanding nodes. First iteration did not reveal any new nodes!');
116119
}
117120
}
118121
}

0 commit comments

Comments
 (0)