Skip to content

Commit

Permalink
chore(demo-cypress): screenshot testing for component tests (#9290)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbarsukov authored Oct 1, 2024
1 parent 8da053b commit a965dd5
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 166 deletions.
2 changes: 1 addition & 1 deletion .github/screenshot-bot.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ branchesIgnore = ["^release/.*", "^v[0-9].x$"]
screenshotImageAttrs = []

# Text which is placed at the beginning of section "Failed tests"
failedTestsReportDescription = '**After <= Diff => Before**'
failedTestsReportDescription = '**Before <= Diff => After**'
2 changes: 1 addition & 1 deletion .github/workflows/e2e-playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- name: Combine images to get diff reports
run: |
npm install canvas
npx ts-node ./scripts/combine-playwright-failed-screenshots.ts
npx ts-node ./scripts/visual-testing/combine-playwright-failed-screenshots.ts
- name: Debug output
continue-on-error: true
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ RELEASE_BODY.md
*tsbuildinfo
.angular
.nx
/projects/demo-cypress/tests-results/
/projects/demo-playwright/tests-results/
/projects/demo-playwright/tests-report/
/projects/demo-playwright/snapshots/
Expand Down
239 changes: 108 additions & 131 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
"ng-packagr": "16.2.3",
"ngx-highlightjs": "10.0.0",
"nx": "19.8.2",
"rxjs": "7.5.0",
"rxjs": "7.8.1",
"standard-version": "9.5.0",
"ts-mockito": "2.6.1",
"ts-node": "10.9.2",
Expand Down
9 changes: 9 additions & 0 deletions projects/demo-cypress/cypress-image-diff.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
ROOT_DIR: 'tests-results',
SCREENSHOTS_DIR: 'snapshots',
REPORT_DIR: '.',
JSON_REPORT: {
FILENAME: 'report-summary',
OVERWRITE: true,
},
};
4 changes: 4 additions & 0 deletions projects/demo-cypress/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {nxComponentTestingPreset} from '@nx/angular/plugins/component-testing';
import {defineConfig} from 'cypress';
import getCompareSnapshotsPlugin from 'cypress-image-diff-js/plugin';

const preset = nxComponentTestingPreset(__filename);

Expand Down Expand Up @@ -44,5 +45,8 @@ export default defineConfig({
indexHtmlFile: 'src/support/component-index.html',
specPattern: 'src/tests/**/*.cy.ts',
experimentalSingleTabRunMode: true,
setupNodeEvents(on, config) {
return getCompareSnapshotsPlugin(on, config);
},
},
});
3 changes: 2 additions & 1 deletion projects/demo-cypress/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"private": true,
"devDependencies": {
"@nx/cypress": "19.8.2",
"cypress": "13.15.0"
"cypress": "13.15.0",
"cypress-image-diff-js": "2.2.1"
}
}
5 changes: 4 additions & 1 deletion projects/demo-cypress/src/support/component-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
<div
data-cy-root
style="padding: 1rem"
></div>
</body>
</html>
3 changes: 3 additions & 0 deletions projects/demo-cypress/src/support/component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {mount} from 'cypress/angular';
import addCompareSnapshotCommand from 'cypress-image-diff-js/command';

addCompareSnapshotCommand();

declare global {
namespace Cypress {
Expand Down
30 changes: 27 additions & 3 deletions projects/demo-cypress/src/tests/input-phone-international.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {provideAnimations} from '@angular/platform-browser/animations';
import {TuiRoot} from '@taiga-ui/core';
import {TUI_ANIMATIONS_SPEED, TuiIcon, TuiRoot} from '@taiga-ui/core';
import type {TuiCountryIsoCode} from '@taiga-ui/i18n';
import {
TuiInputPhoneInternational,
Expand All @@ -21,19 +21,22 @@ import {createOutputSpy} from 'cypress/angular';

@Component({
standalone: true,
imports: [ReactiveFormsModule, TuiInputPhoneInternational, TuiRoot],
imports: [ReactiveFormsModule, TuiIcon, TuiInputPhoneInternational, TuiRoot],
template: `
<tui-root>
<tui-input-phone-international
[countries]="countries"
[formControl]="control"
[(countryIsoCode)]="countryIsoCode"
></tui-input-phone-international>
>
<tui-icon icon="@tui.phone" />
</tui-input-phone-international>
</tui-root>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
provideAnimations(),
{provide: TUI_ANIMATIONS_SPEED, useValue: 0},
tuiInputPhoneInternationalOptionsProvider({
metadata: import('libphonenumber-js/min/metadata').then((m) => m.default),
}),
Expand Down Expand Up @@ -249,6 +252,27 @@ describe('InputPhoneInternational', () => {
});
});
});

describe('debug screenshot testing', () => {
beforeEach(() => {
cy.viewport(400, 300);

cy.mount(Test, {
componentProperties: {
countryIsoCode: 'US',
},
});
});

it('entire page', () => {
cy.get('tui-input-phone-international').click('left');
cy.compareSnapshot('entire-page');
});

it('only textfield', () => {
cy.get('tui-input-phone-international').compareSnapshot('only-textfield');
});
});
});

function initAliases(wrapperSelector: string): void {
Expand Down
1 change: 1 addition & 0 deletions projects/demo-cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
"typeRoots": ["../../node_modules/@types", "../../node_modules/cypress/types"],
"types": ["cypress", "node"]
},
Expand Down
2 changes: 1 addition & 1 deletion projects/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@stackblitz/sdk": "1.11.0",
"@taiga-ui/dompurify": "4.1.7",
"date-fns": "4.1.0",
"rxjs": "7.5.0"
"rxjs": "7.8.1"
},
"devDependencies": {
"@nguniversal/builders": "16.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class="fade"
>
<div tuiFade>Very long value in chip</div>
<div>{{ 123435 | tuiAmount: 'RUB' | async }}</div>
<div>{{ 123456 | tuiAmount: 'RUB' | async }}</div>
</tui-chip>

<tui-chip
Expand Down
47 changes: 47 additions & 0 deletions scripts/visual-testing/combine-cypress-failed-screenshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {readFileSync, writeFileSync} from 'node:fs';

import {combineSnapshots} from './combine-snapshots';

const ROOT_PATH = 'projects/demo-cypress';
const TEST_RESULTS_PATH = `${ROOT_PATH}/tests-results`;

interface TestResult {
status: 'fail' | 'pass';
name: string;
baselinePath: string;
diffPath: string;
comparisonPath: string;
}

interface Report {
suites: Array<{tests: TestResult[]}>;
}

(async function combineCypressFailedScreenshots(): Promise<void> {
const reportSummary = readJSON<Report>(`${TEST_RESULTS_PATH}/report-summary.json`);

if (!reportSummary) {
return;
}

const failedTestSnapshots = reportSummary.suites
.map((x) => x.tests)
.flat()
.filter((x) => x.status === 'fail' && x.diffPath);

for (const {baselinePath, diffPath, comparisonPath, name} of failedTestSnapshots) {
const buffer = await combineSnapshots(
[baselinePath, diffPath, comparisonPath].map((x) => `${ROOT_PATH}/${x}`),
);

writeFileSync(`${TEST_RESULTS_PATH}/${name}.diff.png`, buffer);
}
})();

function readJSON<T>(path: string): T | null {
try {
return JSON.parse(readFileSync(path, 'utf-8'));
} catch {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
// @ts-nocheck It is used in CI only!
/**
* Canvas has difficult installation guide for ARM CPU, including an Apple M1 or M2
* (not friendly for our external contributors).
* https://github.com/Automattic/node-canvas/issues/1511
*/
import {readdirSync, writeFileSync} from 'node:fs';

import {createCanvas, loadImage, version} from 'canvas';
import {combineSnapshots} from './combine-snapshots';

const FAILED_SCREENSHOTS_PATH = 'projects/demo-playwright/tests-results';
const DIFF_IMAGE_POSTFIX = '-diff.png';

console.info('canvas:', version);

(async function combinePlaywrightFailedScreenshots(
rootPath = FAILED_SCREENSHOTS_PATH,
): Promise<void> {
Expand All @@ -35,22 +27,7 @@ console.info('canvas:', version);
return;
}

const images = await Promise.all(imagesPaths.map(loadImage));
const totalWidth = images.reduce((acc: number, {width}) => acc + width, 0);
const maxHeight = Math.max(...images.map(({height}) => height));
const canvas = createCanvas(totalWidth, maxHeight);
const ctx = canvas.getContext('2d');

let prevWidth = 0;

images
.reverse() // After <= Diff => Before
.forEach((image) => {
ctx.drawImage(image, prevWidth, 0);
prevWidth += image.width;
});

const buffer = canvas.toBuffer('image/png');
const buffer = await combineSnapshots(imagesPaths);
const diffImageName = diffImage.split('/').pop()!.replace(DIFF_IMAGE_POSTFIX, '');

writeFileSync(`${rootPath}/${diffImageName}.diff.png`, buffer);
Expand Down
28 changes: 28 additions & 0 deletions scripts/visual-testing/combine-snapshots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @ts-nocheck It is used in CI only!
/**
* Canvas has difficult installation guide for ARM CPU, including an Apple M1 or M2
* (not friendly for our external contributors).
* https://github.com/Automattic/node-canvas/issues/1511
*/
import {createCanvas, loadImage, version} from 'canvas';

export async function combineSnapshots(
imagesPaths: string[],
): Promise<NodeJS.ArrayBufferView> {
console.info('canvas:', version);

const images = await Promise.all(imagesPaths.map(loadImage));
const totalWidth = images.reduce((acc: number, {width}) => acc + width, 0);
const maxHeight = Math.max(...images.map(({height}) => height));
const canvas = createCanvas(totalWidth, maxHeight);
const ctx = canvas.getContext('2d');

let prevWidth = 0;

images.forEach((image) => {
ctx.drawImage(image, prevWidth, 0);
prevWidth += image.width;
});

return canvas.toBuffer('image/png');
}

0 comments on commit a965dd5

Please sign in to comment.