Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add abstraction logic for simplifying writing visual tests #685

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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\\])',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wooo, thats great, I researched if we had something like this for jest.config but I found nothing in the docs, but its great that this is working 🎉

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' });
AlexVelezLl marked this conversation as resolved.
Show resolved Hide resolved
});
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(
AlexVelezLl marked this conversation as resolved.
Show resolved Hide resolved
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 () => {
AlexVelezLl marked this conversation as resolved.
Show resolved Hide resolved
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
Loading