Skip to content

Commit

Permalink
test: create utility function for creating sticker sheet and reduce s…
Browse files Browse the repository at this point in the history
…creenshots (momentum-design#967)

* test(utility): create utility

* test: create utility function for creating sticker sheet

* test: address import type issue

* test: address prebuild error

* test: using import type to prevent circular dependencies

* test: address comment

* test: address comment

* test: address comment

---------

Co-authored-by: Jason Chai Jing <jachai@cisco.com>
Co-authored-by: Deeya Upadhyay <58636958+2020Deeya@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 27, 2024
1 parent 1d0d592 commit 455c3ff
Show file tree
Hide file tree
Showing 214 changed files with 130 additions and 106 deletions.
39 changes: 15 additions & 24 deletions packages/components/config/playwright/public/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,42 @@ main {
width: calc(30%);
height: auto;
background: var(--mds-color-theme-background-gradient-primary-normal);
padding: 10px;
border: 1px solid var(--mds-color-theme-text-primary-normal)
padding: 0.625rem;
border: 0.0625rem solid var(--mds-color-theme-text-primary-normal)
}

.nestedThemeWrapper {
margin-top: 10px;
margin-top: 0.625rem;
display: flex;
flex-direction: column;
width: calc(100% - 20px);
width: calc(100% - 1.25rem);
height: auto;
background: var(--mds-color-theme-background-gradient-primary-normal);
padding: 10px;
border: 1px solid var(--mds-color-theme-text-primary-normal)
padding: 0.625rem;
border: 0.0625rem solid var(--mds-color-theme-text-primary-normal)
}

.themeWrapper+.themeWrapper {
margin-top: 10px;
margin-top: 0.625rem;
}

.colorBox {
width: 100%;
height: 10px;
border-radius: 5px;
height: 0.625rem;
border-radius: 0.3125rem;
}

.colorBox+.colorBox {
margin-top: 10px;
margin-top: 0.625rem;
}

.presence-list {
background-color: var(--mds-color-theme-text-primary-normal);
padding: 10px;
.componentWrapper {
padding: 0.625rem;
}

.presence-row {
.componentRowWrapper {
display: flex;
gap: 10px;
margin-bottom: 5px;
}

.badges-container,
.icon-container,
.focus-ring-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
padding: 5px;
gap: 0.625rem;
margin-bottom: 0.125rem;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Page, expect } from '@playwright/test';
import type { ScreenShotOptions } from '../types';
import type { ComponentsPage } from '..';
import CONSTANTS from '../constants';

interface VisualRegression {
Expand Down Expand Up @@ -35,7 +36,7 @@ class VisualRegression {
threshold: CONSTANTS.VISUAL_REGRESSION.THRESHOLD,
maxDiffPixelRatio: CONSTANTS.VISUAL_REGRESSION.MAX_DIFF_PIXELS_RATIO,
});

// LTR Screenshot
await this.page.evaluate(() => {
document.documentElement.setAttribute('dir', 'ltr');
Expand All @@ -47,6 +48,70 @@ class VisualRegression {
});
}
}

/**
* Creates a sticker sheet on the page, grouping variants of components for visual regression testing.
*
* @param componentsPage - The page object used to interact with the components.
* @param componentTag - The tag name of the component to generate.
* @param attributes - Attributes to apply to the components, with key-value pairs representing attribute
* names and their possible values. The values are defined as an object of key-value pairs.
* @param defaultAttributes - Default attributes that should be applied to every component generated.
* If `children` is provided, it will be used as the inner content of the component.
*
* @returns Locator for the component list containing all generated components.
*/
async createStickerSheet(
componentsPage: ComponentsPage,
componentTag: string,
attributes: Record<string, Record<string, string>>,
defaultAttributes?: Record<string, string>,
) {
const generateComponentMarkup = () => {
if (Object.keys(attributes).length === 0) return '';

const [primaryKey, primaryValues] = Object.entries(attributes)[0];
const otherAttributes = Object.entries(attributes).slice(1);

const defaultAttrs = defaultAttributes
? Object.entries(defaultAttributes)
.filter(([key]) => key !== 'children')
.map(([key, value]) => `${key}="${value}"`)
.join(' ')
: '';
const children = defaultAttributes?.children || '';

return Object.values(primaryValues)
.map((primaryValue) => {
const combinations = otherAttributes.reduce<string[]>(
(acc, [key, values]) =>
acc.flatMap((prev) => Object.values(values).map((currVal) => `${prev} ${key}="${currVal}"`)),
[''],
);
return `<div class="componentRowWrapper">
${combinations.map((combination) => `
<${componentTag} ${defaultAttrs} ${primaryKey}="${primaryValue}" ${combination}>
${children}
</${componentTag}>`).join('')}
</div>`;
})
.join('');
};

await componentsPage.mount({
html: `
<div class="componentWrapper">
${generateComponentMarkup()}
</div>
`,
clearDocument: true,
});

const componentList = componentsPage.page.locator('.componentWrapper');
await componentsPage.page.waitForLoadState('networkidle');

return componentList;
}
}

export default VisualRegression;
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.
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.
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.
4 changes: 2 additions & 2 deletions packages/components/src/components/badge/badge.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Component } from '../../models';
import { TYPE as FONT_TYPE, VALID_TEXT_TAGS } from '../text/text.constants';
import { TYPE as BADGE_TYPE, ICON_NAMES_LIST, DEFAULTS, ICON_VARIANT, ICON_STATE } from './badge.constants';
import styles from './badge.styles';
import IconNames from '../../../../assets/icons/dist/types/types';
import type { IconNames } from '../icon/icon.types';
import type { IconVariant, BadgeType } from './badge.types';
/**
* The `mdc-badge` component is a versatile UI element used to
Expand Down Expand Up @@ -120,7 +120,7 @@ class Badge extends Component {
'mdc-badge-overlay': this.overlay,
[`mdc-badge-icon__${backgroundClassPostfix}`]: true,
})}"
name="${ifDefined(iconName)}"
name="${ifDefined(iconName as IconNames)}"
size="${DEFAULTS.ICON_SIZE}"
></mdc-icon>
`;
Expand Down
9 changes: 4 additions & 5 deletions packages/components/src/components/badge/badge.e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { ComponentsPage, test } from '../../../config/playwright/setup';
import { TYPE, ICON_NAMES_LIST, DEFAULTS, ICON_VARIANT } from './badge.constants';
import type { BadgeType, IconVariant } from './badge.types';
import IconNames from '../../../../assets/icons/dist/types/types';
import type { IconNames } from '../icon/icon.types';

type SetupOptions = {
componentsPage: ComponentsPage;
Expand Down Expand Up @@ -40,7 +40,7 @@ const visualTestingSetup = async (args: SetupOptions) => {

await componentsPage.mount({
html: `
<div class="badges-container">
<div class="componentWrapper componentRowWrapper">
<mdc-badge></mdc-badge>
<mdc-badge type="${TYPE.ICON}" icon-name="${ICON_NAMES_LIST.SUCCESS_ICON_NAME}"></mdc-badge>
<mdc-badge type="${TYPE.COUNTER}" counter="10000" max-counter="999"></mdc-badge>
Expand All @@ -52,7 +52,7 @@ const visualTestingSetup = async (args: SetupOptions) => {
clearDocument: true,
});

const badgesContainer = componentsPage.page.locator('.badges-container');
const badgesContainer = componentsPage.page.locator('.componentRowWrapper');
await badgesContainer.waitFor();
return badgesContainer;
};
Expand All @@ -76,12 +76,11 @@ const testToRun = async (componentsPage: ComponentsPage) => {
await componentsPage.accessibility.checkForA11yViolations('badge-aria-passed-in');
});

const visualBadge = await visualTestingSetup({ componentsPage });

/**
* VISUAL REGRESSION
*/
await test.step('visual-regression', async () => {
const visualBadge = await visualTestingSetup({ componentsPage });
await test.step('matches screenshot of different types element', async () => {
await componentsPage.visualRegression.takeScreenshot('mdc-badge', { element: visualBadge });
});
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.
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.
2 changes: 1 addition & 1 deletion packages/components/src/components/icon/icon.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import styles from './icon.styles';
import { Component } from '../../models';
import providerUtils from '../../utils/provider';
import IconProvider from '../iconprovider/iconprovider.component';
import IconNames from '../../../../assets/icons/dist/types/types';
import { dynamicSVGImport } from './icon.utils';
import { DEFAULTS } from './icon.constants';
import type { IconNames } from './icon.types';

/**
* Icon component that dynamically displays SVG icons based on a valid name.
Expand Down
9 changes: 4 additions & 5 deletions packages/components/src/components/icon/icon.e2e-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from '@playwright/test';
import { ComponentsPage, test } from '../../../config/playwright/setup';
import IconNames from '../../../../assets/icons/dist/types/types';
import type { IconNames } from './icon.types';

type SetupOptions = {
componentsPage: ComponentsPage;
Expand Down Expand Up @@ -32,15 +32,15 @@ const visualTestingSetup = async (args: SetupOptions) => {
const { componentsPage, ...restArgs } = args;
await componentsPage.mount({
html: `
<div class="icon-container">
<div class="componentWrapper componentRowWrapper">
<mdc-icon name="${restArgs.name}"></mdc-icon>
<mdc-icon name="${restArgs.name}" size="2"></mdc-icon>
<mdc-icon name="${restArgs.name}" size="2" style="--mdc-icon-fill-color: red;"></mdc-icon>
</div>
`,
clearDocument: true,
});
const icon = componentsPage.page.locator('.icon-container');
const icon = componentsPage.page.locator('.componentRowWrapper');
await icon.waitFor();
return icon;
};
Expand All @@ -67,12 +67,11 @@ test('mdc-icon', async ({ componentsPage }) => {
await componentsPage.accessibility.checkForA11yViolations('icon-aria-passed-in');
});

const visualIcons = await visualTestingSetup({ componentsPage, name });

/**
* VISUAL REGRESSION
*/
await test.step('visual-regression', async () => {
const visualIcons = await visualTestingSetup({ componentsPage, name });
await test.step('matches screenshot of elements with default, size equal 2 and color set to red', async () => {
await componentsPage.visualRegression.takeScreenshot('mdc-icon', { element: visualIcons });
});
Expand Down
3 changes: 3 additions & 0 deletions packages/components/src/components/icon/icon.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type IconNames from '@momentum-design/icons/dist/types/types';

export type { IconNames };
35 changes: 5 additions & 30 deletions packages/components/src/components/presence/presence.e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,16 @@ const setup = async (args: SetupOptions) => {
return presence;
};

const visualTestingSetup = async (args: SetupOptions) => {
const { componentsPage } = args;
const presences = (type: string) =>
Object.values(SIZE)
.map((size) => `<mdc-presence type="${type}" size="${size}"></mdc-presence>`)
.join('');

await componentsPage.mount({
html: `
<div class="presence-list">
${Object.values(TYPE)
.map(
(type) => `
<div class="presence-row">
${presences(type)}
</div>
`,
)
.join('')}
</div>
`,
});
const presenceList = componentsPage.page.locator('.presence-list');
const presence = componentsPage.page.locator('.mdc-presence').last();
await presence.waitFor();
return presenceList;
};

const testToRun = async (componentsPage: ComponentsPage) => {
const visualPresence = await visualTestingSetup({ componentsPage });

/**
* VISUAL REGRESSION
*/
await test.step('visual-regression', async () => {
const visualPresence = await componentsPage.visualRegression.createStickerSheet(componentsPage, 'mdc-presence', {
type: TYPE,
size: SIZE,
});

await test.step('matches screenshot of default element', async () => {
await componentsPage.visualRegression.takeScreenshot('mdc-presence', { element: visualPresence });
});
Expand Down
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Loading

0 comments on commit 455c3ff

Please sign in to comment.