Skip to content

Commit

Permalink
Merge pull request #685 from KshitijThareja/vt-abstraction
Browse files Browse the repository at this point in the history
Add abstraction logic for simplifying writing visual tests
  • Loading branch information
AlexVelezLl authored Jul 26, 2024
2 parents 3ef3ee1 + 93f2b7c commit 9e45f45
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 157 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ esLintConfig.settings['import/resolver'].nuxt = {
nuxtSrcDir: 'docs',
};

// Remove linting errors for the globals defined in the jest-puppeteer package
// Remove linting errors for the globals defined in the jest-puppeteer package and testUtils
esLintConfig.globals = {
...esLintConfig.globals,
page: true,
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
yarn --frozen-lockfile
npm rebuild node-sass
- name: Run tests
run: yarn test
run: yarn test
1 change: 1 addition & 0 deletions jest.conf/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const moduleNameMapper = {
module.exports = {
rootDir: path.resolve(__dirname, '..'),
moduleFileExtensions: ['js', 'json', 'vue'],
testNamePattern: '^(?!.*\\[Visual\\])',
moduleNameMapper,
testEnvironment: 'jsdom',
testEnvironmentOptions: {
Expand Down
9 changes: 9 additions & 0 deletions jest.conf/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ global.afterEach(() => {
});
});

// Configure special test blocks for visual tests
global.describe.visual = (name, fn) => {
global.describe(`[Visual] ${name}`, fn);
};

global.it.visual = (name, fn) => {
global.it(`[Visual] ${name}`, fn);
};

// Register Vue plugins and components
Vue.use(VueRouter);
Vue.use(VueCompositionAPI);
Expand Down
11 changes: 0 additions & 11 deletions jest.conf/testUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ export const resizeWindow = (width, height = 768) => {
});
};

export function canTakeScreenshot() {
const percyToken = process.env.PERCY_TOKEN;
const runVisualTests = process.env.TEST_TYPE === 'visual';
if (runVisualTests && !percyToken) {
throw new Error(
'Error: Visual tests cannot be run because PERCY_TOKEN environment variable is not set.'
);
}
return runVisualTests && percyToken;
}

export const testAfterResize = testFunction => {
let animationFrameId;
const assertAfterResize = () => {
Expand Down
1 change: 1 addition & 0 deletions jest.conf/visual.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ module.exports = async () => {
preset: 'jest-puppeteer',
testTimeout: 50000,
moduleFileExtensions: ['js', 'json', 'vue'],
testNamePattern: '\\[Visual\\]',
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css)$': path.resolve(
__dirname,
Expand Down
5 changes: 5 additions & 0 deletions jest.conf/visual.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ import { percySnapshot } from '@percy/puppeteer';
// Set the test type to visual
process.env.TEST_TYPE = 'visual';

const TESTING_PLAYGROUND_URL = 'http://localhost:4000/testing-playground';
global.percySnapshot = percySnapshot;

global.beforeAll(async () => {
await page.goto(TESTING_PLAYGROUND_URL, { waitUntil: 'networkidle2' });
});
47 changes: 47 additions & 0 deletions jest.conf/visual.testUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import percySnapshot from '@percy/puppeteer';

export async function renderComponent(component, props) {
const beforeRenderState = await page.evaluate(() => {
const testing_playground = document.querySelector('#testing-playground');
return testing_playground ? testing_playground.innerHTML : '';
});

await page.evaluate(
({ component, props }) => {
window.postMessage(
{
type: 'RENDER_COMPONENT',
component: component,
props: props,
},
'*'
);
},
{ component, props }
);
await page.waitForSelector('#testing-playground');

// Wait until the innerHTML of the testing playground changes, indicating that the component has been rendered.
await page.waitForFunction(
initialState => {
const testing_playground = document.querySelector('#testing-playground');
return testing_playground && testing_playground.innerHTML !== initialState;
},
{},
beforeRenderState
);

// Check if the component has been rendered by comparing the initial state with the current state.
const isComponentRendered = await page.evaluate(initialState => {
const testing_playground = document.querySelector('#testing-playground');
return testing_playground && testing_playground.innerHTML !== initialState;
}, beforeRenderState);

global.expect(isComponentRendered).toBe(true);
}

export async function takeSnapshot(name) {
if (process.env.TEST_TYPE == 'visual') {
await percySnapshot(page, name);
}
}
146 changes: 56 additions & 90 deletions lib/buttons-and-links/__tests__/KButton.spec.js
Original file line number Diff line number Diff line change
@@ -1,109 +1,75 @@
import { shallowMount } from '@vue/test-utils';
import percySnapshot from '@percy/puppeteer';
import KButton from '../KButton.vue';
import { canTakeScreenshot } from '../../../jest.conf/testUtils';
import { renderComponent, takeSnapshot } from '../../../jest.conf/visual.testUtils';

describe('KButton', () => {
if (!canTakeScreenshot()) {
describe('icon related props', () => {
it('should render an icon before the text with the icon string passed to the `icon` prop', () => {
const wrapper = shallowMount(KButton, {
propsData: {
icon: 'add',
},
});
expect(wrapper.find('[data-test="iconBefore"]').exists()).toBe(true);
describe('icon related props', () => {
it('should render an icon before the text with the icon string passed to the `icon` prop', () => {
const wrapper = shallowMount(KButton, {
propsData: {
icon: 'add',
},
});
it('should render an icon after the text with the icon string pased to the `iconAfter` prop', () => {
const wrapper = shallowMount(KButton, {
propsData: {
iconAfter: 'video',
},
});
expect(wrapper.find('[data-test="iconAfter"]').exists()).toBe(true);
expect(wrapper.find('[data-test="iconBefore"]').exists()).toBe(true);
});
it('should render an icon after the text with the icon string pased to the `iconAfter` prop', () => {
const wrapper = shallowMount(KButton, {
propsData: {
iconAfter: 'video',
},
});
it('should render a dropdown icon when hasDropdown is true', () => {
const wrapper = shallowMount(KButton, {
propsData: {
hasDropdown: true,
},
});
expect(wrapper.find('[data-test="dropdownIcon"]').exists()).toBe(true);
expect(wrapper.find('[data-test="iconAfter"]').exists()).toBe(true);
});
it('should render a dropdown icon when hasDropdown is true', () => {
const wrapper = shallowMount(KButton, {
propsData: {
hasDropdown: true,
},
});
expect(wrapper.find('[data-test="dropdownIcon"]').exists()).toBe(true);
});
});

describe('text prop and slots', () => {
it('should render the text prop if nothing is in the default slot', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
});
expect(wrapper.text()).toContain('test');
describe('text prop and slots', () => {
it('should render the text prop if nothing is in the default slot', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
});
expect(wrapper.text()).toContain('test');
});

it('should render the slot when the slot has content', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
slots: {
default: '<span>slot</span>',
},
});
expect(wrapper.text()).toContain('slot');
expect(wrapper.text()).toContain('test');
it('should render the slot when the slot has content', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
slots: {
default: '<span>slot</span>',
},
});
expect(wrapper.text()).toContain('slot');
expect(wrapper.text()).toContain('test');
});
});

describe('event handling', () => {
it('should emit a click event when clicked', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
});
wrapper.trigger('click');
expect(wrapper.emitted().click).toBeTruthy();
describe('event handling', () => {
it('should emit a click event when clicked', () => {
const wrapper = shallowMount(KButton, {
propsData: {
text: 'test',
},
});
wrapper.trigger('click');
expect(wrapper.emitted().click).toBeTruthy();
});
} else {
describe('KButton Visual Tests', () => {
beforeAll(async () => {
await page.goto('http://localhost:4000/testing-playground', { waitUntil: 'networkidle2' });
});
});

async function renderComponent(component, props) {
await page.evaluate(
({ component, props }) => {
window.postMessage(
{
type: 'RENDER_COMPONENT',
component: component,
props: props,
},
'*'
);
},
{ component, props }
);
await page.waitForSelector('#testing-playground');

const isComponentRendered = await page.evaluate(() => {
const testing_playground = document.querySelector('#testing-playground');
return testing_playground && testing_playground.children.length > 0;
});

if (!isComponentRendered) {
// eslint-disable-next-line no-console
console.error('Component did not render in the testing playground');
}
}

it('renders correctly with default props', async () => {
await renderComponent('KButton', { text: 'Test Button' });
await percySnapshot(page, 'KButton - Default');
});
describe.visual('KButton Visual Tests', () => {
it('renders correctly with default props', async () => {
await renderComponent('KButton', { text: 'Test Button' });
await takeSnapshot('KButton - Default');
});
}
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"precompile-svgs": "node utils/precompileSvgs/index.js && yarn run pregenerate",
"precompile-custom-svgs": "node utils/precompileSvgs/index.js --custom && yarn run pregenerate",
"_lint-watch-fix": "yarn lint -w -m",
"test:percy": "PERCY_LOGLEVEL=error npx percy exec -v -- jest --config jest.conf/visual.index.js -i ./lib/buttons-and-links/__tests__/KButton.spec.js",
"test:percy": "PERCY_LOGLEVEL=info npx percy exec -v -- jest --config jest.conf/visual.index.js -i ./lib/buttons-and-links/__tests__/KButton.spec.js",
"test": "jest --config=jest.conf/index.js",
"test:visual": "concurrently --kill-others --success first --names \"SERVER,TEST\" -c \"bgCyan.bold,bgYellow.bold\" \"yarn dev-only > /dev/null 2>&1\" \"yarn test:percy\"",
"_api-watch": "chokidar \"**/lib/**\" -c \"node utils/extractApi.js\""
Expand Down
Loading

0 comments on commit 9e45f45

Please sign in to comment.