-
Notifications
You must be signed in to change notification settings - Fork 87
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
Percy and jest-puppeteer environment setup for visual testing #670
Changes from 6 commits
9abf33e
9b01670
f01b65d
a538bf2
1ffca8e
d9e1d14
519dd63
2b06d96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
version: 2 | ||
snapshot: | ||
widths: | ||
- 375 | ||
- 1280 | ||
minHeight: 1024 | ||
percyCSS: "" | ||
enableJavaScript: false #disable Javascript by default to capture initial page state without JS-driven changes | ||
cliEnableJavaScript: true #enable Javascript when running Percy through CLI, for dynamic content | ||
disableShadowDOM: false | ||
discovery: | ||
allowedHostnames: [] | ||
disallowedHostnames: [] | ||
networkIdleTimeout: 100 | ||
captureMockedServiceWorker: false | ||
upload: | ||
files: "**/*.{png,jpg,jpeg}" | ||
ignore: "" | ||
stripExtensions: false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<template> | ||
|
||
<!-- | ||
Testing Playground: A dedicated page for component visual testing | ||
***************************************************** | ||
|
||
Please do not modify the contents of this file. | ||
--> | ||
<div id="testing-playground" style="padding: 24px"> | ||
<component :is="component" v-bind="componentProps" /> | ||
</div> | ||
|
||
</template> | ||
|
||
|
||
<script> | ||
|
||
/** | ||
* Renders the components for visual testing | ||
* to ensure expected visual behavior under | ||
* various conditions. | ||
*/ | ||
export default { | ||
name: 'VisualTestingPlayground', | ||
data() { | ||
return { | ||
/** | ||
* @type {string|null} The name of the component to be dynamically rendered. | ||
*/ | ||
component: null, | ||
/** | ||
* @type {Object} The props to be passed to the dynamically rendered component. | ||
*/ | ||
componentProps: {}, | ||
}; | ||
}, | ||
|
||
/** | ||
* Adds an event listener for messages from the test runner. | ||
* This listener will trigger the `handleMessage` method. | ||
*/ | ||
mounted() { | ||
window.addEventListener('message', this.handleMessage); | ||
}, | ||
|
||
/** | ||
* Removes the event listener for messages from the test runner. | ||
*/ | ||
beforeDestroy() { | ||
window.removeEventListener('message', this.handleMessage); | ||
}, | ||
|
||
methods: { | ||
/** | ||
* Handles messages received from the test runner to render a specified component. | ||
* @param {MessageEvent} event - The message event containing the component and its props. | ||
*/ | ||
handleMessage(event) { | ||
if (event.data.type === 'RENDER_COMPONENT') { | ||
this.component = event.data.component; | ||
this.componentProps = event.data.props; | ||
} | ||
}, | ||
}, | ||
}; | ||
|
||
</script> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
launch: { | ||
headless: true, | ||
timeout: 180000, | ||
}, | ||
browserContext: 'default', | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const path = require('node:path'); | ||
|
||
const moduleNameMapper = { | ||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|css)$': path.resolve( | ||
__dirname, | ||
'./fileMock.js' | ||
), | ||
}; | ||
|
||
module.exports = { | ||
rootDir: path.resolve(__dirname, '..'), | ||
preset: 'jest-puppeteer', | ||
testTimeout: 50000, | ||
moduleFileExtensions: ['js', 'json', 'vue'], | ||
moduleNameMapper, | ||
transform: { | ||
'^.+\\.js$': require.resolve('babel-jest'), | ||
'^.+\\.vue$': require.resolve('vue-jest'), | ||
}, | ||
snapshotSerializers: ['jest-serializer-vue'], | ||
globals: { | ||
HOST: 'http://localhost:4000/', | ||
'vue-jest': { | ||
hideStyleWarn: true, | ||
experimentalCSSCompile: true, | ||
}, | ||
}, | ||
setupFilesAfterEnv: [path.resolve(__dirname, './visual.setup')], | ||
verbose: true, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import './setup'; | ||
import { percySnapshot } from '@percy/puppeteer'; | ||
|
||
// Set the test type to visual | ||
process.env.TEST_TYPE = 'visual'; | ||
|
||
global.percySnapshot = percySnapshot; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,17 @@ | ||
import { shallowMount } from '@vue/test-utils'; | ||
import KImg from '../'; | ||
|
||
function makeWrapper(opts) { | ||
return shallowMount(KImg, opts); | ||
function makeWrapper({ propsData = {} } = {}) { | ||
return shallowMount(KImg, { propsData }); | ||
} | ||
|
||
describe('KImg', () => { | ||
it(`renders without any errors when a valid 'src' and 'altText' are provided`, () => { | ||
const error = jest.fn(); | ||
const wrapper = makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo' }, | ||
listeners: { error }, | ||
}); | ||
|
||
expect(wrapper.exists()).toBe(true); | ||
expect(error).not.toHaveBeenCalled(); | ||
|
||
const img = wrapper.find('img'); | ||
expect(img.exists()).toBe(true); | ||
|
@@ -23,26 +20,20 @@ describe('KImg', () => { | |
}); | ||
|
||
it(`throws an error when no 'altText' is provided`, () => { | ||
const error = jest.fn(); | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: undefined }, | ||
listeners: { error }, | ||
}); | ||
expect(error).toHaveBeenCalled(); | ||
expect(error.mock.calls[0][0]).toBeInstanceOf(Error); | ||
expect(error.mock.calls[0][0].message).toBe( | ||
'Missing required prop - provide altText or indicate isDecorative' | ||
); | ||
expect(() => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @KshitijThareja! I think we can let this file without modifications. In this case it seems it is the expected behaviour to pass an error listener to catch these errors. See #645 (comment). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I'll make the changes. I was currently working on finding a way to use |
||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: undefined }, | ||
}) | ||
).toThrow(); | ||
}); | ||
|
||
describe(`when no 'altText' is provided and it is a decorative image`, () => { | ||
it(`does not throw an error`, () => { | ||
const error = jest.fn(); | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: undefined, isDecorative: true }, | ||
listeners: { error }, | ||
}); | ||
expect(error).not.toHaveBeenCalled(); | ||
expect(() => | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: undefined, isDecorative: true }, | ||
}) | ||
).not.toThrow(); | ||
}); | ||
|
||
it(`sets 'alt' attribute to an empty string`, () => { | ||
|
@@ -55,38 +46,32 @@ describe('KImg', () => { | |
}); | ||
|
||
it(`throws an error when 'aspectRatio' has an invalid format`, () => { | ||
const error = jest.fn(); | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo', aspectRatio: '16/9' }, | ||
listeners: { error }, | ||
}); | ||
expect(error).toHaveBeenCalled(); | ||
expect(error.mock.calls[0][0]).toBeInstanceOf(Error); | ||
expect(error.mock.calls[0][0].message).toBe('Invalid aspect ratio provided: 16/9'); | ||
expect(() => | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo', aspectRatio: '16/9' }, | ||
}) | ||
).toThrow(); | ||
}); | ||
|
||
it(`doesn't throw an error when 'aspectRatio' has a valid format`, () => { | ||
const error = jest.fn(); | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo', aspectRatio: '16:9' }, | ||
listeners: { error }, | ||
}); | ||
expect(error).not.toHaveBeenCalled(); | ||
expect(() => | ||
makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo', aspectRatio: '16:9' }, | ||
}) | ||
).not.toThrow(); | ||
}); | ||
|
||
it(`emits an 'error' event when there is an error in loading the image`, async () => { | ||
const error = jest.fn(); | ||
it(`emits an 'error' event when there is an error in loading the image`, () => { | ||
const wrapper = makeWrapper({ | ||
propsData: { src: '/le-logo.svg', altText: 'LE logo' }, | ||
listeners: { error }, | ||
}); | ||
|
||
// Manually trigger the onError method to simulate the image load failure | ||
const e = new Event('error'); | ||
wrapper.vm.onError(e); | ||
wrapper.vm.onError(new Event('error')); | ||
|
||
expect(error).toHaveBeenCalled(); | ||
expect(error.mock.calls[0][0]).toBeInstanceOf(Event); | ||
expect(error.mock.calls[0][0]).toEqual(e); | ||
// Check if the "error" event has been emitted with the DOM event payload | ||
const emittedEvent = wrapper.emitted().error; | ||
expect(emittedEvent).toBeTruthy(); | ||
expect(emittedEvent[0][0]).toBeInstanceOf(Event); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think we should add this here, because this lint config is for the whole package, including the development environment where we are not running jest, and I think this could cause unwanted behaviours. Probably we could set these settings in a specific file for tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @KshitijThareja! why do we need this property here in the esLintConfig env?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had followed troubleshooting guidelines as specified here: Jest-Puppeteer
This was basically to recognize jest's global variables and not flag their use while running linting tests.
But on taking a closer look now, I can see that this property has already been included in the main esLint config import (
kolibri-tools/.eslintrc
). I'll make the required changes.