Skip to content

Commit

Permalink
fix: add gui e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowusr committed Sep 21, 2023
1 parent 0696935 commit 719da9f
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 31 deletions.
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"e2e:build-browsers": "docker build -f test/func/docker/Dockerfile -t html-reporter-browsers:0.0.1 --network host test/func/docker",
"e2e:build-packages": "npm run --workspace=test/func/packages --if-present build",
"e2e:generate-fixtures": "npm run --workspace=test/func/fixtures generate",
"e2e:launch-browsers": "docker run -it --rm --network=host --add-host=host.docker.internal:host-gateway html-reporter-browsers:0.0.1",
"e2e:launch-browsers": "docker run -it --rm --network=host --add-host=host.docker.internal:127.0.0.1 html-reporter-browsers:0.0.1",
"e2e:test": "npm run --workspace=test/func/tests test",
"e2e": "npm run e2e:build-packages && npm run e2e:generate-fixtures ; npm run e2e:test",
"lint": "eslint .",
Expand Down Expand Up @@ -181,6 +181,7 @@
"styled-components": "^3.4.10",
"stylus": "^0.57.0",
"stylus-loader": "^3.0.2",
"tree-kill": "^1.2.2",
"ts-node": "^10.9.1",
"type-fest": "^3.13.1",
"typescript": "^5.0.4",
Expand Down
9 changes: 7 additions & 2 deletions test/func/tests/.hermione.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const serverHost = process.env.SERVER_HOST ?? 'host.docker.internal';
const serverPort = process.env.SERVER_PORT ?? 8083;
const projectUnderTest = process.env.PROJECT_UNDER_TEST;

const isRunningGuiTests = projectUnderTest.includes('gui');

if (!projectUnderTest) {
throw 'Project under test was not specified';
}
Expand All @@ -27,6 +29,9 @@ module.exports = {
common: {
files: 'common/**/*.hermione.js'
},
'common-gui': {
files: 'common-gui/**/*.hermione.js'
},
eye: {
files: 'eye/**/*.hermione.js',
},
Expand All @@ -51,7 +56,7 @@ module.exports = {

plugins: {
'html-reporter-test-server': {
enabled: true,
enabled: !isRunningGuiTests,
port: serverPort
},
'html-reporter-tester': {
Expand All @@ -61,7 +66,7 @@ module.exports = {
},

'hermione-global-hook': {
beforeEach: async function() {
beforeEach: isRunningGuiTests ? undefined : async function() {
await this.browser.url(this.browser.options.baseUrl);
await this.browser.execute(() => {
document.querySelectorAll('.section').forEach((section) => {
Expand Down
187 changes: 160 additions & 27 deletions test/func/tests/common-gui/index.hermione.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,196 @@
const childProcess = require('child_process');
const fs = require('fs/promises');
const path = require('path');
const {promisify} = require('util');

const treeKill = promisify(require('tree-kill'));

const {PORTS} = require('../../utils/constants');
const {getTestSectionByName} = require('../utils');

const projectName = process.env.PROJECT_UNDER_TEST;
const projectDir = path.resolve(__dirname, '../../fixtures', projectName);
const guiUrl = `http://host.docker.internal:${PORTS[projectName].gui}`;

const reportDir = path.join(projectDir, 'report');
const reportBackupDir = path.join(projectDir, 'report');
const reportBackupDir = path.join(projectDir, 'report-backup');

const runGui = async () => {
return new Promise((resolve, reject) => {
const child = childProcess.spawn('npm', ['run', 'gui'], {cwd: projectDir});

let timeoutId = setTimeout(() => {
treeKill(child.pid).then(() => {
reject(new Error('Couldn\'t start GUI: timed out'));
});
}, 3000);

child.stdout.on('data', (data) => {
if (data.toString().includes('GUI is running at')) {
clearTimeout(timeoutId);
resolve(child);
}
});

child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});

child.on('close', (code) => {
if (code !== 0) {
reject(new Error(`GUI process exited with code ${code}`));
}
});
});
};

// These tests should not be launched in parallel
describe('GUI mode', () => {
let guiProcess;

beforeEach(async ({browser}) => {
await fs.cp(reportDir, reportBackupDir);
await fs.cp(reportDir, reportBackupDir, {recursive: true});

guiProcess = childProcess.spawn('npm run gui', {cwd: projectDir});
guiProcess = await runGui();

await browser.url(guiUrl);
await browser.$('button*=Expand all').click();
});

afterEach(async () => {
guiProcess.kill('SIGINT');
afterEach(async ({browser}) => {
await browser.execute(() => {
window.localStorage.clear();
});

await treeKill(guiProcess.pid);

await fs.rmdir(reportDir);
await fs.rm(reportDir, {recursive: true, force: true, maxRetries: 3});
await fs.rename(reportBackupDir, reportDir);
// TODO: restore project directory via git

childProcess.execSync('git restore .', {cwd: path.join(projectDir, 'screens')});
childProcess.execSync('git clean -dfx .', {cwd: path.join(projectDir, 'screens')});
});

describe('running tests', () => {
it('should run a single test');
});
it('should run a single test', async ({browser}) => {
const retryButton = await browser.$([
getTestSectionByName('successful test'),
'//button[contains(text(), "Retry")]'
].join(''));

describe('accepting diff', () => {
it('should create a successful retry');
it('should make the test pass on next run');
});
await retryButton.click();
await retryButton.waitForClickable({timeout: 10000});

describe('undo accepting diff', () => {
beforeEach(() => {
// TODO: accept diff
});
// Should be passed
const testSection = await browser.$(getTestSectionByName('successful test'));
const testSectionClassNames = (await testSection.getAttribute('class')).split(' ');

it('should leave project files intact');
});
expect(testSectionClassNames).toContain('section_status_success');

// History should appear
const historySelector = [getTestSectionByName('successful test'), '//details[.//summary[contains(text(), "History")]]'].join('');

await browser.$(historySelector).click();
const historyText = await browser.$(historySelector + '/div').getText();

describe('accepting new screenshot', () => {
it('should create a successful retry');
it('should make the test pass on next run');
expect(historyText.includes('<-')).toBeTruthy();
});
});

describe('undo accepting new screenshot', () => {
beforeEach(() => {
// TODO: accept new screenshot
for (const testName of ['diff', 'no ref']) {
const fullTestName = `test with ${testName}`;
describe(`accepting ${fullTestName}`, () => {
beforeEach(async ({browser}) => {
await browser.$(getTestSectionByName(fullTestName)).scrollIntoView();

const acceptButton = await browser.$([
getTestSectionByName(fullTestName),
'//button[contains(text(), "Accept")]'
].join(''));

await acceptButton.click();

// Waiting a bit for the GUI to save images
await new Promise(resolve => setTimeout(resolve, 500));
});

it('should create a successful retry', async ({browser}) => {
const allRetryButtonsSelector = [
getTestSectionByName(fullTestName),
'//button[@data-test-id="retry-switcher"]'
].join('');
const retrySwitcher = browser.$(`(${allRetryButtonsSelector})[last()]`);

await retrySwitcher.assertView('retry-switcher');

const testSection = await browser.$(getTestSectionByName(fullTestName));
const testSectionClassNames = (await testSection.getAttribute('class')).split(' ');

expect(testSectionClassNames).toContain('section_status_success');
});

it('should make the test pass on next run', async ({browser}) => {
const retryButton = await browser.$([
getTestSectionByName(fullTestName),
'//button[contains(text(), "Retry")]'
].join(''));

await retryButton.click();
await retryButton.waitForClickable({timeout: 10000});

// Verify green retry button
const allRetryButtonsSelector = [
getTestSectionByName(fullTestName),
'//button[@data-test-id="retry-switcher"]'
].join('');
const retrySwitcher = browser.$(`(${allRetryButtonsSelector})[last()]`);

await retrySwitcher.assertView('retry-switcher');

// Verify green test section
const testSection = await browser.$(getTestSectionByName(fullTestName));
const testSectionClassNames = (await testSection.getAttribute('class')).split(' ');

expect(testSectionClassNames).toContain('section_status_success');
});
});
}

it('should leave project files intact');
});
for (const testName of ['diff', 'no ref']) {
const fullTestName = `test with ${testName}`;
describe(`undo accepting ${fullTestName}`, () => {
beforeEach(async ({browser}) => {
await browser.$(getTestSectionByName(fullTestName)).scrollIntoView();

const acceptButton = await browser.$([
getTestSectionByName(fullTestName),
'//button[contains(text(), "Accept")]'
].join(''));

await acceptButton.click();

// Waiting a bit for the GUI to save images
await new Promise(resolve => setTimeout(resolve, 500));
});

it('should leave project files intact', async ({browser}) => {
const gitDiffBeforeUndo = childProcess.execSync('git status . --porcelain=v2', {cwd: path.join(projectDir, 'screens')});

const undoButton = await browser.$([
getTestSectionByName(fullTestName),
'//button[contains(text(), "Undo")]'
].join(''));

await undoButton.click();

// Waiting a bit for the GUI to save images
await new Promise(resolve => setTimeout(resolve, 500));

const gitDiffAfterUndo = childProcess.execSync('git status . --porcelain=v2', {cwd: path.join(projectDir, 'screens')});

expect(gitDiffBeforeUndo.length > 0).toBeTruthy();
expect(gitDiffAfterUndo.length === 0).toBeTruthy();
});
});
}
});
1 change: 1 addition & 0 deletions test/func/tests/common/test-results-appearance.hermione.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe('Test results appearance', () => {

describe('Test with no ref image', function() {
it('should have pink retry selector', async ({browser}) => {
// TODO
const retrySelectorButton = await browser.$('//div[contains(text(),\'test without screenshot\')]/..//button[@data-test-id="retry-switcher"]');

await hideHeader(browser);
Expand Down
4 changes: 3 additions & 1 deletion test/func/tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"scripts": {
"gui:hermione-common": "PROJECT_UNDER_TEST=hermione npx hermione --set common gui",
"gui:hermione-eye": "PROJECT_UNDER_TEST=hermione-eye npx hermione --no --set eye gui",
"gui:hermione-gui": "PROJECT_UNDER_TEST=hermione-gui npx hermione --no --set common-gui gui",
"gui:playwright": "PROJECT_UNDER_TEST=playwright npx hermione --set common gui",
"gui:plugins": "PROJECT_UNDER_TEST=plugins SERVER_PORT=8084 npx hermione --set plugins gui",
"hermione:hermione": "conc npm:hermione:*",
"hermione:hermione": "conc npm:hermione:hermione*",
"hermione:hermione-common": "PROJECT_UNDER_TEST=hermione npx hermione --set common",
"hermione:hermione-eye": "PROJECT_UNDER_TEST=hermione-eye npx hermione --set common",
"hermione:hermione-gui": "PROJECT_UNDER_TEST=hermione-gui npx hermione --no --set common-gui",
"hermione:playwright": "PROJECT_UNDER_TEST=playwright npx hermione --set common",
"hermione:plugins": "PROJECT_UNDER_TEST=plugins SERVER_PORT=8084 npx hermione --set plugins",
"test": "conc npm:hermione:*"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 719da9f

Please sign in to comment.