Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit 07ab71f

Browse files
ci: DEV-2482: E2E playwright (#648)
* DEV-2482: Migrate to Playwright * Enable action * Fix workflow * Fix run command * Run with workers * FIx yarn cache * Update Codecept config * Enable codecoverage * Fix timeout * Parallelize "Images' labels type matching" test * Use suites for tests execution * Increase actions timeout * Split modules instalation and e2e * Yarn cache e2e * Add Istanbul converter for the code coverage * Fix tests * Increace timeouts * Fix NER test * Update build flow * Fix image test * Fix cache key * Restart browser after each test Decrease number of workers to match CPU cores * Rename coverage converter * Gather CPU cores number * Fix coverage upload * DIsable coverage collection * Update codecept config * Disable coverage upload * Freeze lockfile for yarn installation * Remove unnecessary apuse * remove pause from globals Co-authored-by: hlomzik <[email protected]>
1 parent 44995c2 commit 07ab71f

36 files changed

+5470
-18969
lines changed

.github/workflows/build.yml

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ jobs:
2121
with:
2222
node-version: "${{ env.NODE }}"
2323

24+
- name: Get CPU info
25+
id: "cpu-info"
26+
run: echo "::set-output name=cores-count::$(cat /proc/cpuinfo | grep processor | wc -l)"
27+
2428
- name: Upgrade Yarn
2529
run: npm install -g [email protected]
2630

@@ -32,16 +36,18 @@ jobs:
3236
uses: actions/cache@v3
3337
with:
3438
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
35-
key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/package.json') }}-${{ hashFiles('**/yarn.lock') }}
39+
key: ${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-${{ hashFiles('**/yarn.lock') }}
40+
restore-keys: |
41+
yarn-${{ env.CACHE_NAME_PREFIX }}-${{ runner.os }}-node-${{ env.NODE }}-
3642
3743
- name: Print Yarn cache size
3844
run: du -d 0 -h ${{ steps.yarn-cache-dir-path.outputs.dir }}
3945

4046
- name: Install dependencies
41-
run: rm package-lock.json && yarn install
47+
run: yarn install --frozen-lockfile
4248

4349
- name: Unit tests
44-
run: yarn test
50+
run: yarn test && yarn test:coverage
4551

4652
- name: Build distribution package
4753
timeout-minutes: 10
@@ -53,17 +59,49 @@ jobs:
5359
# run http-server with build in background (will be killed after job ends)
5460
# do this only for master branch (so only for push event)
5561
# because pr can contain unfinished job
56-
- run: npx serve -l tcp://localhost:3000 build &
62+
- name: "Run server"
63+
run: npx serve -l tcp://localhost:3000 build &
5764
if: github.event_name == 'push'
65+
5866
- id: wait_for_npx_server
67+
name: "Wait for server"
5968
timeout-minutes: 1
6069
run: |
6170
while [ "$(curl -s -o /dev/null -L -w ''%{http_code}'' "http://localhost:3000/")" != "200" ]; do
6271
echo "=> Waiting for service to become available" && sleep 2s
6372
done
64-
- run: yarn run test:e2e:headless
65-
timeout-minutes: 15
73+
74+
- name: "Setup e2e"
75+
if: github.event_name == 'push'
76+
timeout-minutes: 1
77+
run: |
78+
set -euo pipefail
79+
cd e2e
80+
yarn install --frozen-lockfile
81+
82+
- name: Run e2e test suite
6683
if: github.event_name == 'push'
84+
timeout-minutes: 30
85+
run: |
86+
set -euo pipefail
87+
cd e2e
88+
yarn run test:ci ${{ steps.cpu-info.outputs.cores-count }}
89+
90+
# - name: "Convert coverage report to Istanbul"
91+
# if: github.event_name == 'push'
92+
# run: |
93+
# set -euo pipefail
94+
# cd e2e
95+
# yarn run coverage:istanbul
96+
# yarn run coverage:report
97+
98+
# - name: "Upload e2e coverage to Codecov"
99+
# uses: codecov/[email protected]
100+
# with:
101+
# name: codecov-general
102+
# directory: ./e2e/output/coverage
103+
# token: ${{ secrets.CODECOV_TOKEN }}
104+
# fail_ci_if_error: true
67105

68106
- uses: actions/upload-artifact@v3
69107
if: ${{ failure() }}

codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fixes:
2+
- "::src/"

e2e/codecept.conf.js

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,28 @@
22
// HEADLESS=true npx codecept run
33
const headless = process.env.HEADLESS;
44

5-
// codecept can make screenshot on every step and create html
6-
// with "gif" and annotations. possible usage:
7-
// GIF=true yarn e2e:test:headless e2e/present_feature_test.js
8-
const recordVideo = process.env.GIF
9-
? {
10-
stepByStepReport: {
11-
enabled: true,
12-
deleteSuccessful: false,
13-
},
14-
}
15-
: null;
16-
17-
// eslint-disable-next-line no-undef
18-
exports.config = {
19-
tests: "./**/*.test.js",
5+
module.exports.config = {
6+
timeout: 60 * 30, // Time out after 30 minutes
7+
tests: "./tests/*.test.js",
208
output: "./output",
219
helpers: {
22-
Puppeteer: {
10+
// Puppeteer: {
11+
// url: "http://localhost:3000",
12+
// show: !headless,
13+
// waitForAction: headless ? 100 : 1200,
14+
// windowSize: "1200x900",
15+
// },
16+
Playwright: {
2317
url: "http://localhost:3000",
2418
show: !headless,
19+
restart: 'context',
20+
timeout: 60000, // Action timeout after 60 seconds
2521
waitForAction: headless ? 100 : 1200,
2622
windowSize: "1200x900",
23+
waitForNavigation: "networkidle",
24+
browser: "chromium",
25+
trace: false,
26+
keepTraceForPassedTests: false,
2727
},
2828
MouseActions: {
2929
require: "./helpers/MouseActions.js",
@@ -45,15 +45,34 @@ exports.config = {
4545
ErrorsCollector: "./fragments/ErrorsCollector.js",
4646
},
4747
bootstrap: null,
48-
mocha: {},
48+
mocha: {
49+
bail: true,
50+
reporterOptions: {
51+
mochaFile: "output/result.xml",
52+
},
53+
},
4954
name: "label-studio-frontend",
5055
plugins: {
5156
retryFailedStep: {
5257
enabled: true,
58+
minTimeout: 100,
59+
defaultIgnoredSteps: [
60+
//'amOnPage',
61+
//'wait*',
62+
'send*',
63+
'execute*',
64+
'run*',
65+
'have*',
66+
],
5367
},
68+
// For the future generations
69+
// coverage: {
70+
// enabled: true,
71+
// coverageDir: "output/coverage",
72+
// },
5473
screenshotOnFail: {
5574
enabled: true,
5675
},
57-
...recordVideo,
5876
},
77+
require: ['ts-node/register'],
5978
};

e2e/coverage-to-istanbul.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const fs = require('fs/promises');
2+
const path = require('path');
3+
4+
const v8toIstanbul = require('v8-to-istanbul');
5+
const covDir = './output/coverage';
6+
7+
const convertCoverage = async (fileName) => {
8+
if (fileName.match('istanbul')) return;
9+
10+
const coverage = require(`${covDir}/${fileName}`);
11+
const basename = path.basename(fileName).replace('.coverage.json', '');
12+
const finalName = `${covDir}/${basename}_final.coverage.json`;
13+
14+
for (const entry of coverage) {
15+
// Used to get file name
16+
const file = entry.url.match(/(?:http(s)*:\/\/.*\/)(?<file>.*)/);
17+
const converter = new v8toIstanbul(file.groups.file, 0, {
18+
source: entry.source,
19+
});
20+
21+
await converter.load();
22+
converter.applyCoverage(entry.functions);
23+
24+
// Store converted coverage file which can later be used to generate report
25+
await fs.writeFile(
26+
finalName,
27+
JSON.stringify(converter.toIstanbul(), null, 2),
28+
);
29+
console.log(`Processed ${basename}`);
30+
}
31+
32+
await fs.unlink(`${covDir}/${fileName}`);
33+
};
34+
35+
// read all the coverage file from output/coverage folder
36+
fs.readdir(covDir).then(files => {
37+
files.forEach(convertCoverage);
38+
});

e2e/fragments/AtAudioView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ const Helpers = require("../tests/helpers");
55

66
module.exports = {
77
waitForAudio() {
8-
I.executeAsyncScript(Helpers.waitForAudio);
8+
I.executeScript(Helpers.waitForAudio);
99
},
1010
};

e2e/fragments/AtImageView.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,31 @@ module.exports = {
2121
},
2222

2323
waitForImage() {
24-
I.executeAsyncScript(Helpers.waitForImage);
24+
I.say("Waiting for image to be loaded");
25+
I.executeScript(Helpers.waitForImage);
2526
I.waitForVisible("canvas", 5);
2627
},
2728

2829
async getCanvasSize() {
29-
const sizes = await I.executeAsyncScript(Helpers.getCanvasSize);
30+
const sizes = await I.executeScript(Helpers.getCanvasSize);
3031

3132
return sizes;
3233
},
3334

3435
async getImageSize() {
35-
const sizes = await I.executeAsyncScript(Helpers.getImageSize);
36+
const sizes = await I.executeScript(Helpers.getImageSize);
3637

3738
return sizes;
3839
},
3940

4041
async getImageFrameSize() {
41-
const sizes = await I.executeAsyncScript(Helpers.getImageFrameSize);
42+
const sizes = await I.executeScript(Helpers.getImageFrameSize);
4243

4344
return sizes;
4445
},
4546

4647
setZoom(scale, x, y) {
47-
I.executeAsyncScript(Helpers.setZoom, scale, x, y);
48+
I.executeScript(Helpers.setZoom, [scale, x, y]);
4849
},
4950

5051
/**
@@ -53,22 +54,22 @@ module.exports = {
5354
* @param {number} y
5455
*/
5556
clickKonva(x, y) {
56-
I.executeAsyncScript(Helpers.clickKonva, x, y);
57+
I.executeScript(Helpers.clickKonva, [x, y]);
5758
},
5859
/**
5960
* Click multiple times on the main Stage
6061
* @param {number[][]} points
6162
*/
6263
clickPointsKonva(points) {
63-
I.executeAsyncScript(Helpers.clickMultipleKonva, points);
64+
I.executeScript(Helpers.clickMultipleKonva, points);
6465
},
6566
/**
6667
* Click multiple times on the main Stage then close Polygon
6768
* @param {number[][]} points
6869
* @deprecated Use drawByClickingPoints instead
6970
*/
7071
clickPolygonPointsKonva(points) {
71-
I.executeAsyncScript(Helpers.polygonKonva, points);
72+
I.executeScript(Helpers.polygonKonva, points);
7273
},
7374
/**
7475
* Dragging between two points
@@ -79,7 +80,7 @@ module.exports = {
7980
* @deprecated Use drawByDrag instead
8081
*/
8182
dragKonva(x, y, shiftX, shiftY) {
82-
I.executeAsyncScript(Helpers.dragKonva, x, y, shiftX, shiftY);
83+
I.executeScript(Helpers.dragKonva, [x, y, shiftX, shiftY]);
8384
},
8485

8586
/**
@@ -90,33 +91,33 @@ module.exports = {
9091
* @param {number} tolerance
9192
*/
9293
async hasPixelColor(x, y, rgbArray, tolerance = 3) {
93-
const colorPixels = await I.executeAsyncScript(Helpers.getKonvaPixelColorFromPoint, x, y);
94+
const colorPixels = await I.executeScript(Helpers.getKonvaPixelColorFromPoint, [x, y]);
9495
const hasPixel = Helpers.areEqualRGB(rgbArray, colorPixels, tolerance);
9596

9697
return hasPixel;
9798
},
9899

99100
// Only for debugging
100101
async whereIsPixel(rgbArray, tolerance = 3) {
101-
const points = await I.executeAsyncScript(Helpers.whereIsPixel, rgbArray, tolerance);
102+
const points = await I.executeScript(Helpers.whereIsPixel, [rgbArray, tolerance]);
102103

103104
return points;
104105
},
105106

106107
async countKonvaShapes() {
107-
const count = await I.executeAsyncScript(Helpers.countKonvaShapes);
108+
const count = await I.executeScript(Helpers.countKonvaShapes);
108109

109110
return count;
110111
},
111112

112113
async isTransformerExist() {
113-
const isTransformerExist = await I.executeAsyncScript(Helpers.isTransformerExist);
114+
const isTransformerExist = await I.executeScript(Helpers.isTransformerExist);
114115

115116
return isTransformerExist;
116117
},
117118

118119
async isRotaterExist() {
119-
const isRotaterExist = await I.executeAsyncScript(Helpers.isRotaterExist);
120+
const isRotaterExist = await I.executeScript(Helpers.isRotaterExist);
120121

121122
return isRotaterExist;
122123
},
@@ -190,6 +191,7 @@ module.exports = {
190191
clickAt(x, y) {
191192
I.scrollPageToTop();
192193
I.clickAt(this._stageBBox.x + x, this._stageBBox.y + y);
194+
I.wait(1); // We gotta wait here because clicks on the canvas are not processed immediately
193195
},
194196
dblClickAt(x, y) {
195197
I.scrollPageToTop();

e2e/fragments/AtRichText.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const Helpers = require("../tests/helpers");
66
module.exports = {
77
_rootSelector: ".lsf-htx-richtext",
88
selectTextByGlobalOffset(startOffset, endOffset) {
9-
I.executeAsyncScript(Helpers.selectText, {
9+
I.executeScript(Helpers.selectText, {
1010
selector: ".lsf-htx-richtext",
1111
rangeStart: startOffset,
1212
rangeEnd: endOffset,

e2e/fragments/AtSidebar.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const { assert } = require("assert");
2+
13
/* global inject, locate */
24
const { I } = inject();
35

@@ -9,16 +11,16 @@ module.exports = {
911
_selectedRegionsLocator: locate(".lsf-entity"),
1012
seeRegions(count) {
1113
if (count) {
12-
I.see(`Regions\n\u00A0${count}`, this._sideBarLocator);
14+
I.seeElement(this._regionsCounterLocator.withText(`${count}`));
1315
} else {
1416
I.seeElement(this._regionGroupButton.withText("Regions"));
1517
I.dontSeeElement(this._regionGroupButton.withDescendant(this._regionsCounterLocator));
1618
}
1719
},
1820
dontSeeRegions(count) {
1921
if (count) {
20-
I.dontSee(`Regions\n\u00A0${count}`, this._sideBarLocator);
21-
} else if (count===+count) {
22+
I.dontSeeElement(this._regionsCounterLocator.withText(`${count}`));
23+
} else if (count === +count) {
2224
I.seeElement(this._regionGroupButton.withDescendant(this._regionsCounterLocator));
2325
} else {
2426
I.dontSee("Regions", this._sideBarLocator);
@@ -51,4 +53,7 @@ module.exports = {
5153
clickRegion(text) {
5254
I.click(this._regionLocator.withText(`${text}`));
5355
},
56+
selectTool(tool) {
57+
I.click(`[aria-label=${tool}-tool]`);
58+
},
5459
};

0 commit comments

Comments
 (0)