diff --git a/features/visual-compare/uar/quiz.spec.js b/features/visual-compare/uar/quiz.spec.js new file mode 100644 index 00000000..52e2a806 --- /dev/null +++ b/features/visual-compare/uar/quiz.spec.js @@ -0,0 +1,14 @@ +module.exports = { + name: 'UAR Visual Comparison', + features: [ + { + tcid: '0', + name: '@quiz screenshots', + path: '/drafts/quiz/quiz-2/', + stable: '@milo_live', + beta: '@uar_live', + tags: '@cc @uar-quiz-stable-vs-beta @uar-quiz-static', + data: 'data/uar/quiz/quiz-basic.yml', + }, + ], +}; diff --git a/libs/timestamp.js b/libs/timestamp.js new file mode 100644 index 00000000..10f332a5 --- /dev/null +++ b/libs/timestamp.js @@ -0,0 +1,6 @@ +const fs = require('fs'); + +fs.writeFileSync( + 'timestamp.json', + JSON.stringify([(new Date()).toLocaleString()], null, 2), +); diff --git a/libs/webutil.js b/libs/webutil.js index c451441a..ee2f703a 100644 --- a/libs/webutil.js +++ b/libs/webutil.js @@ -155,33 +155,33 @@ exports.WebUtil = class WebUtil { * @param {Object} attProps - The attribute properties and expected values to verify. * @returns {Boolean} - True if all attribute properties match the expected values, false otherwise. */ - async verifyAttributes_(locator, attProps) { - this.locator = locator; - let result = true; - await Promise.allSettled( - Object.entries(attProps).map(async ([property, expectedValue]) => { - if (property === 'class' && typeof expectedValue === 'string') { - // If the property is 'class' and the expected value is an string, - // split the string value into individual classes - const classes = expectedValue.split(' '); - try { - await expect(await this.locator).toHaveClass(classes.join(' ')); - } catch (error) { - console.error('Attribute class not found:', error); - result = false; - } - } else { - try { - await expect(await this.locator).toHaveAttribute(property, expectedValue); - } catch (error) { - console.error(`Attribute ${property} not found:`, error); - result = false; - } + async verifyAttributes_(locator, attProps) { + this.locator = locator; + let result = true; + await Promise.allSettled( + Object.entries(attProps).map(async ([property, expectedValue]) => { + if (property === 'class' && typeof expectedValue === 'string') { + // If the property is 'class' and the expected value is an string, + // split the string value into individual classes + const classes = expectedValue.split(' '); + try { + await expect(await this.locator).toHaveClass(classes.join(' ')); + } catch (error) { + console.error('Attribute class not found:', error); + result = false; } - }), - ); - return result; - } + } else { + try { + await expect(await this.locator).toHaveAttribute(property, expectedValue); + } catch (error) { + console.error(`Attribute ${property} not found:`, error); + result = false; + } + } + }), + ); + return result; + } /** * Slow/fast scroll of entire page JS evaluation method, aides with lazy loaded content. @@ -292,16 +292,16 @@ exports.WebUtil = class WebUtil { await this.page.screenshot({ path: `${folderPath}/${fileName}`, fullPage: true }); } - async takeScreenshotAndCompare(urlA, urlB, folderPath, fileName) { + async takeScreenshotAndCompare(urlA, callbackA, urlB, callbackB, folderPath, fileName) { console.info(`[Test Page]: ${urlA}`); await this.page.goto(urlA); - await this.page.waitForTimeout(3000); + await callbackA(); await this.page.screenshot({ path: `${folderPath}/${fileName}-a.png`, fullPage: true }); const baseImage = fs.readFileSync(`${folderPath}/${fileName}-a.png`); console.info(`[Test Page]: ${urlB}`); await this.page.goto(urlB); - await this.page.waitForTimeout(3000); + await callbackB(); await this.page.waitForSelector('.feds-footer-privacyLink'); await this.page.screenshot({ path: `${folderPath}/${fileName}-b.png`, fullPage: true }); const currImage = fs.readFileSync(`${folderPath}/${fileName}-b.png`); @@ -314,4 +314,23 @@ exports.WebUtil = class WebUtil { console.info('Differences found'); } } + + static compareScreenshots(stableArray, betaArray, folderPath) { + const comparator = getComparator('image/png'); + for (let i = 0; i < stableArray.length; i += 1) { + if (betaArray[i].slice(-10) === stableArray[i].slice(-10)) { + const stableImage = fs.readFileSync(`${folderPath}/${stableArray[i]}`); + const betaImage = fs.readFileSync(`${folderPath}/${betaArray[i]}`); + const diffImage = comparator(stableImage, betaImage); + + if (diffImage) { + fs.writeFileSync(`${folderPath}/${stableArray[i]}-diff.png`, diffImage.diff); + console.info('Differences found'); + } + } else { + console.info('Screenshots are not matched'); + console.info(`${stableArray[i]} vs ${betaArray[i]}`); + } + } + } }; diff --git a/selectors/uar/quiz.page.js b/selectors/uar/quiz.page.js index 9a759670..218bac1f 100644 --- a/selectors/uar/quiz.page.js +++ b/selectors/uar/quiz.page.js @@ -11,6 +11,7 @@ export default class Quiz { this.uarResult = page.locator('.quiz-results h1'); this.uarResult2 = page.locator('//div[contains(@data-path,"marquee-product")]//strong | //div[contains(@data-path,"check-bullet")]//h1 | //div[contains(@data-path,"express-product")]//h1'); this.uarResult3 = page.locator('//div[contains(@data-path,"card")]//strong'); + this.screenshots = []; } /** @@ -41,7 +42,7 @@ export default class Quiz { * @param {string} url * @param {string} originalAnswer */ - async clickEachAnswer(url, originalAnswer, keyNumber, isScreenshot = false) { + async clickEachAnswer(url, originalAnswer, keyNumber, version, isScreenshot = false) { await this.page.goto(url); const answers = originalAnswer.split('>').map((x) => x.trim()); @@ -64,13 +65,15 @@ export default class Quiz { const index = answers.indexOf(answer); const folderPath = 'screenshots/uar'; - const desktopName = `${keyNumber} - new - desktop - ${index} - ${answer.replace('/', '')}.png`; - const tabletName = `${keyNumber} - new - tablet - ${index} - ${answer.replace('/', '')}.png`; - const mobileName = `${keyNumber} - new - mobile - ${index} - ${answer.replace('/', '')}.png`; + const desktopName = `${keyNumber} - ${version} - desktop - ${index} - ${answer.replace('/', '')}.png`; + const tabletName = `${keyNumber} - ${version} - tablet - ${index} - ${answer.replace('/', '')}.png`; + const mobileName = `${keyNumber} - ${version} - mobile - ${index} - ${answer.replace('/', '')}.png`; await this.webUtil.takeScreenshot(folderPath, desktopName, 1920, 1080); await this.webUtil.takeScreenshot(folderPath, tabletName, 768, 1024); await this.webUtil.takeScreenshot(folderPath, mobileName, 375, 812); + + this.screenshots.push(desktopName, tabletName, mobileName); } // click next button @@ -79,13 +82,15 @@ export default class Quiz { await this.page.waitForTimeout(500); const index = answers.length - 1; const folderPath = 'screenshots/uar'; - const desktopName = `${keyNumber} - new - desktop - ${index} - ${answer.replace('/', '')}.png`; - const tabletName = `${keyNumber} - new - tablet - ${index} - ${answer.replace('/', '')}.png`; - const mobileName = `${keyNumber} - new - mobile - ${index} - ${answer.replace('/', '')}.png`; + const desktopName = `${keyNumber} - ${version} - desktop - ${index} - ${answer.replace('/', '')}.png`; + const tabletName = `${keyNumber} - ${version} - tablet - ${index} - ${answer.replace('/', '')}.png`; + const mobileName = `${keyNumber} - ${version} - mobile - ${index} - ${answer.replace('/', '')}.png`; await this.webUtil.takeScreenshot(folderPath, desktopName, 1920, 1080); await this.webUtil.takeScreenshot(folderPath, tabletName, 768, 1024); await this.webUtil.takeScreenshot(folderPath, mobileName, 375, 812); + + this.screenshots.push(desktopName, tabletName, mobileName); } } @@ -97,7 +102,7 @@ export default class Quiz { * Validate products on result page to match with expect products * @param {string} name */ - async checkResultPage(name, originalAnswer, keyNumber, isScreenshot = false) { + async checkResultPage(name, originalAnswer, keyNumber, version, isScreenshot = false) { const newProduct = []; const actualProduct = await this.uarResult.nth(0); @@ -163,13 +168,15 @@ export default class Quiz { await this.page.waitForTimeout(1000); const folderPath = 'screenshots/uar'; - const desktopName = `${keyNumber} - new - desktop - result.png`; - const tabletName = `${keyNumber} - new - tablet - result.png`; - const mobileName = `${keyNumber} - new - mobile - result.png`; + const desktopName = `${keyNumber} - ${version} - desktop - result.png`; + const tabletName = `${keyNumber} - ${version} - tablet - result.png`; + const mobileName = `${keyNumber} - ${version} - mobile - result.png`; await this.webUtil.takeScreenshot(folderPath, desktopName, 1920, 1080); await this.webUtil.takeScreenshot(folderPath, tabletName, 768, 1024); await this.webUtil.takeScreenshot(folderPath, mobileName, 375, 812); + + this.screenshots.push(desktopName, tabletName, mobileName); } console.info(`==========new============\n${newProduct.sort().join('')}`); diff --git a/tests/uar/quiz.screenshots.test.js b/tests/uar/quiz.screenshots.test.js index ca21cbf9..67fa75dc 100644 --- a/tests/uar/quiz.screenshots.test.js +++ b/tests/uar/quiz.screenshots.test.js @@ -41,11 +41,11 @@ test.describe('Quiz flow test suite', () => { }); await test.step(`New: Select each answer on test page according to ${key}`, async () => { - await quiz.clickEachAnswer(url, key, keyNumber, true); + await quiz.clickEachAnswer(url, key, keyNumber, 'new', true); }); await test.step('New: Check results on test page', async () => { - newProduct = await quiz.checkResultPage(testdata[key], key, keyNumber, true); + newProduct = await quiz.checkResultPage(testdata[key], key, keyNumber, 'new', true); }); expect.soft(newProduct).toContain(oldProduct); diff --git a/visual-compare-tests/caas/cards.vc.test.js b/visual-compare-tests/caas/cards.vc.test.js index 29a2a253..98ab3ed9 100644 --- a/visual-compare-tests/caas/cards.vc.test.js +++ b/visual-compare-tests/caas/cards.vc.test.js @@ -16,10 +16,10 @@ test.describe('Milo Caas block visual comparison test suite', () => { // eslint-disable-next-line no-restricted-syntax for (const feature of features) { // eslint-disable-next-line no-loop-func - test(`${feature.name},${feature.tags}`, async ({ baseURL }) => { + test(`${feature.name},${feature.tags}`, async ({ page, baseURL }) => { const folderPath = 'screenshots/caas'; // eslint-disable-next-line max-len - await webUtil.takeScreenshotAndCompare(baseURL + feature.stable, baseURL + feature.beta, folderPath, feature.name); + await webUtil.takeScreenshotAndCompare(baseURL + feature.stable, async () => { await page.waitForTimeout(3000); }, baseURL + feature.beta, async () => { await page.waitForTimeout(3000); }, folderPath, feature.name); }); } }); diff --git a/visual-compare-tests/uar/quiz.vc.test.js b/visual-compare-tests/uar/quiz.vc.test.js new file mode 100644 index 00000000..028e5920 --- /dev/null +++ b/visual-compare-tests/uar/quiz.vc.test.js @@ -0,0 +1,66 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-loop-func */ +/* eslint-disable no-restricted-syntax */ +import { expect, test } from '@playwright/test'; +import Quiz from '../../selectors/uar/quiz.page.js'; + +const { features } = require('../../features/visual-compare/uar/quiz.spec.js'); +const { WebUtil } = require('../../libs/webutil.js'); +const envs = require('../../envs/envs.js'); + +test.describe('Quiz flow test suite', () => { + // reset timeout because we use this to run all test data + test.setTimeout(10 * 60 * 1000); + for (const feature of features) { + test( + `${feature.name}, ${feature.tags}`, + async ({ page }) => { + const stablePage = new Quiz(page); + const betaPage = new Quiz(page); + const stableURL = `${envs[feature.stable]}${feature.path}`; + console.info(stableURL); + const betaURL = `${envs[feature.beta]}${feature.path}`; + console.info(betaURL); + + // load test data from static files + const testdata = await WebUtil.loadTestData(`${feature.data}`); + + let keyNumber = 0; + + for (const key of Object.keys(testdata)) { + console.log(key); + let stableProduct = ''; + let betaProduct = ''; + let stableProductScreenshots = []; + let betaProductScreenshots = []; + keyNumber += 1; + await test.step(`Old: Select each answer on test page according to ${key}`, async () => { + await stablePage.clickEachAnswer(stableURL, key, keyNumber, 'stable', true); + }); + + await test.step('Old: Check results on test page', async () => { + stableProduct = await stablePage.checkResultPage(testdata[key], key, keyNumber, 'stable', true); + }); + + stableProductScreenshots = stablePage.screenshots.slice(); + stablePage.screenshots = []; + + await test.step(`New: Select each answer on test page according to ${key}`, async () => { + await betaPage.clickEachAnswer(betaURL, key, keyNumber, 'beta', true); + }); + + await test.step('New: Check results on test page', async () => { + betaProduct = await betaPage.checkResultPage(testdata[key], key, keyNumber, 'beta', true); + }); + + betaProductScreenshots = betaPage.screenshots.slice(); + betaPage.screenshots = []; + + WebUtil.compareScreenshots(stableProductScreenshots, betaProductScreenshots, 'screenshots/uar'); + + expect.soft(betaProduct).toContain(stableProduct); + } + }, + ); + } +});