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

Introduce visual testing mechanism to KDS #742

Open
wants to merge 68 commits into
base: nodejs-upgrade
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
9abf33e
Setup initial configuration
KshitijThareja Jun 19, 2024
9b01670
Add and configure percy for visual tests
KshitijThareja Jun 21, 2024
f01b65d
Change server configuration and file names for visual tests
KshitijThareja Jun 29, 2024
a538bf2
Add component rendering mechanism for visual testing
KshitijThareja Jun 30, 2024
1ffca8e
Remove unused packages
KshitijThareja Jun 30, 2024
d9e1d14
Add JSDoc for testing playground and modify server script
KshitijThareja Jul 2, 2024
519dd63
Update KImg unit tests and eslint configuration
KshitijThareja Jul 6, 2024
64301f8
Use concurrently to manage startup and termination of tests
KshitijThareja Jul 9, 2024
759ef28
Update waitForServer function to use http
KshitijThareja Jul 10, 2024
1bc6ea1
Remove unused dependencies and separate visual tests from yarn test
KshitijThareja Jul 10, 2024
2b06d96
Separate visual tests from yarn test command
KshitijThareja Jul 10, 2024
905e770
Merge pull request #670 from KshitijThareja/visual-testing
bjester Jul 10, 2024
65a08d4
Introduce abstraction for component rendering functions and support f…
KshitijThareja Jul 11, 2024
d2ea3d1
Initial workflow config
KshitijThareja Jul 14, 2024
8ce5fe8
Fix the initial workflow for visual tests job
KshitijThareja Jul 16, 2024
fde157f
Merge branch 'gsoc/visual-testing' into vt-add-concurrently
KshitijThareja Jul 16, 2024
3ef3ee1
Merge pull request #677 from KshitijThareja/vt-add-concurrently
bjester Jul 16, 2024
2eaabdb
Add automatic comments job for visual testing workflow
KshitijThareja Jul 17, 2024
310f75b
Fix lint errors
KshitijThareja Jul 17, 2024
f6c66f3
Abstraction for page navigation
KshitijThareja Jul 17, 2024
1eba82f
Fix lint errors
KshitijThareja Jul 17, 2024
c37ddd3
Merge branch 'gsoc/visual-testing' into vt-abstraction
KshitijThareja Jul 17, 2024
f4373a0
Introduce visual testing to the existing Javascript tests workflow
KshitijThareja Jul 17, 2024
bdea16d
Introduce visual testing to the existing Javascript tests workflow
KshitijThareja Jul 17, 2024
eb95513
Introduce visual testing to the existing Javascript tests workflow
KshitijThareja Jul 17, 2024
c0e977f
Replace usage of custom describe blocks with test regex patterns
KshitijThareja Jul 20, 2024
95228a2
Merge branch 'vt-abstraction' of github.com:KshitijThareja/kolibri-de…
KshitijThareja Jul 20, 2024
61c98f9
Remove unintentional changes
KshitijThareja Jul 20, 2024
fde97db
Add comments for rendering function and make variable names clear
KshitijThareja Jul 23, 2024
ad6c1d5
Remove run server step
KshitijThareja Jul 23, 2024
c18b8d5
Remove pipes where not required
KshitijThareja Jul 24, 2024
93f2b7c
Add special test blocks for visual tests to append Visual tag automat…
KshitijThareja Jul 26, 2024
9e45f45
Merge pull request #685 from KshitijThareja/vt-abstraction
AlexVelezLl Jul 26, 2024
c6a3ab3
Add support for default slots rendering
KshitijThareja Jul 31, 2024
19654cf
Change workflow to run on separate environment
KshitijThareja Aug 6, 2024
0821b88
Merge branch 'gsoc/visual-testing' into vt-ghaction
KshitijThareja Aug 6, 2024
dacba7c
Merge pull request #686 from KshitijThareja/vt-ghaction
AlexVelezLl Aug 6, 2024
fe94675
Add option to specify custom dimensions for snapshots
KshitijThareja Aug 6, 2024
d921297
Add puppeteer config for CI environments
KshitijThareja Aug 6, 2024
2e713bc
Remove .puppeteerrc.cjs
KshitijThareja Aug 7, 2024
61d3645
Merge pull request #712 from KshitijThareja/vt-ghaction
AlexVelezLl Aug 7, 2024
97dbb74
Checkout pr head
AlexVelezLl Aug 7, 2024
bf8a172
Update should skip to include style changes
AlexVelezLl Aug 7, 2024
9b4861d
Initial documentation for visual tests
KshitijThareja Aug 9, 2024
10a1d40
Update comment job to use github-script action
KshitijThareja Aug 9, 2024
6dc4cab
Update visual testing docs with links to mentioned files
KshitijThareja Aug 9, 2024
981aab9
Update documentation
KshitijThareja Aug 13, 2024
0ff28b9
Updat comment job to use utils file
KshitijThareja Aug 13, 2024
d536c84
Migrate findComment and generateComment functions to githubUtils.js
KshitijThareja Aug 13, 2024
cdbf6ca
Add reference to documentation in getting-started doc
KshitijThareja Aug 13, 2024
1a28076
Merge pull request #720 from KshitijThareja/vt-ghaction
AlexVelezLl Aug 14, 2024
05809de
Update jsdoc
AlexVelezLl Aug 14, 2024
855a5a2
Merge branch 'gsoc/visual-testing' into vt-test
AlexVelezLl Aug 14, 2024
d1ead28
Merge branch 'gsoc/visual-testing' into vt-test
AlexVelezLl Aug 14, 2024
798dd24
Modify testing playground to render named and default slots in a sing…
KshitijThareja Aug 15, 2024
b84eafa
Add jsdocs for renderComponent and takeSnapshot functions
KshitijThareja Aug 15, 2024
384e593
Fix lint error
KshitijThareja Aug 15, 2024
9471e37
Remove async from describe block
KshitijThareja Aug 15, 2024
6f3ab99
Merge branch 'vt-test' of https://github.com/KshitijThareja/kolibri-d…
KshitijThareja Aug 15, 2024
625cff0
Add snapshot options specifying test widths
KshitijThareja Aug 16, 2024
3bddaab
Merge pull request #710 from KshitijThareja/vt-test
AlexVelezLl Aug 16, 2024
5c079b5
Update test command to work for all test files
KshitijThareja Aug 18, 2024
80cceae
Merge branch 'gsoc/visual-testing' into vt-doc
KshitijThareja Aug 20, 2024
176a971
Add additional info for rendering complex components
KshitijThareja Aug 20, 2024
6e06337
Add link to percy documentation
KshitijThareja Aug 21, 2024
33435a3
Update info for usage of test blocks for visual tests
KshitijThareja Aug 21, 2024
28e3c40
Update required node version specification
KshitijThareja Aug 22, 2024
ac305aa
Merge pull request #721 from KshitijThareja/vt-doc
AlexVelezLl Aug 23, 2024
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
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,14 @@ esLintConfig.settings['import/resolver'].nuxt = {
nuxtSrcDir: 'docs',
};

// Remove linting errors for the globals defined in the jest-puppeteer package and testUtils
esLintConfig.globals = {
...esLintConfig.globals,
page: true,
browser: true,
context: true,
puppeteerConfig: true,
jestPuppeteer: true,
};

module.exports = esLintConfig;
43 changes: 43 additions & 0 deletions .github/githubUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
async function generateComment(percyUrl) {
return `
### Percy Visual Test Results

**Percy Dashboard:** [View Detailed Report](${percyUrl})

**Environment:**
- **Node.js Version:** 18.x
- **OS:** Ubuntu-latest

**Instructions for Reviewers:**
- Click on the [Percy Dashboard](${percyUrl}) link to view detailed visual diffs.
- Review the visual changes highlighted in the report.
- Approve or request changes based on the visual differences.
`;
}

async function findComment(github, context, issue_number) {
let comment;
let page = 1
while (!comment) {
const request = await github.rest.issues.listComments({
issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
page,
})
const comments = request.data
if (!comments.length) {
return;
}
comment = comments.find(c => c.body && c.body.includes('### Percy Visual Test Results'));
if (comment) {
return comment.id.toString()
}
page += 1;
}
}

module.exports = {
findComment,
generateComment,
}
109 changes: 109 additions & 0 deletions .github/workflows/visual_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Percy Visual Tests

on: [pull_request_target]

jobs:
pre_job:
name: Path match check
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@master
with:
github_token: ${{ github.token }}
paths: '["**.vue", "**.js", "**.css", "**.scss", "yarn.lock"]'

visual_tests:
name: Frontend Visual Tests
needs: pre_job
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
runs-on: ubuntu-latest
environment: percy_tests
outputs:
percy_url: ${{ steps.extract-url.outputs.percy_url }}
steps:
- name: Checkout code from PR
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: '**/node_modules'
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.OS }}-node-
- name: Install dependencies
run: |
yarn --frozen-lockfile
npm rebuild node-sass
- name: Download Chromium
run: npx puppeteer browsers install chrome
- name: Extract jsdocs and environment info
run: yarn pregenerate
- name: Run visual tests
run: yarn test:visual 2>&1 | tee test-output.log
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
- name: Extract Percy build URL
id: extract-url
run: |
url=$(grep -o 'https://percy.io/[a-zA-Z0-9/_-]*' test-output.log | tail -1)
echo "percy_url=$url" >> $GITHUB_OUTPUT

comment:
name: Comment Percy results
needs: visual_tests
if: ${{ needs.visual_tests.result == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Checkout code from PR
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Define comment body
id: comment-text
uses: actions/github-script@v7
with:
script: |
const percyUrl = "${{ needs.visual_tests.outputs.percy_url }}";
const utils = require('./.github/githubUtils.js');
return await utils.generateComment(percyUrl);
- name: Find existing comment
id: find-comment
uses: actions/github-script@v7
with:
script: |
const utils = require('./.github/githubUtils.js');
return await utils.findComment(github, context, context.issue.number);
- name: Create build comment
if: ${{!steps.find-comment.outputs.result}}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: ${{ steps.comment-text.outputs.result }}
})
- name: Update build comment
if: ${{steps.find-comment.outputs.result}}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{steps.find-comment.outputs.result}},
body: ${{ steps.comment-text.outputs.result }}
})
19 changes: 19 additions & 0 deletions .percy.yml
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
1 change: 1 addition & 0 deletions dev_docs/01_getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ You're now ready to code!
- If you'd like to update the component library, continue to [How to update the component library](./03_how_to_update_library.md).

The guidelines referenced above should be sufficient for the most common tasks. There are a few additional developer documentation pages available. However, these pages contain information that is more internal in nature or related to specialized tasks:
- [Visual Testing](./07_visual_testing_guide.md)
- [How to update the documentation website](./04_how_to_update_docs.md)
- [Icons](./05_icons.md)
- [Miscellaneous](./06_misc.md)
131 changes: 131 additions & 0 deletions dev_docs/07_visual_testing_guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Visual Testing

KDS has a visual testing system that allows you to take snapshots of how KDS Components would look like in different browsers, and compare them with the previously set baseline versions of these components, allowing you to see the visual differences in a PR's changes more quickly.

## Prerequisites

- Make sure that your Node version is >= 18.x with all the requirements (listed in package.json) installed.
- Ensure that you declare the `PERCY_TOKEN` environment variable when running visual tests locally.
- You should not have anything running on `port:4000` as the visual testing server utilizes that port to run the tests.

## Guide to using the visual testing mechanism

### Running visual tests locally

1. **Setup**:
- Ensure all dependencies are installed:

```bash
yarn install
```

- Set the `PERCY_TOKEN` environment variable:

```bash
export PERCY_TOKEN=<your-percy-token>
```

2. **Run Visual Tests**:
- Execute the tests with the visual testing configuration:

```bash
yarn test:visual
```

3. **Check results**:
- Head over to the Percy Dashboard and check the results for the latest Percy build.

### Visual testing workflow

We use GitHub Actions to execute the visual testing workflow on every PR. When changes to a PR are made, a deployment request is initiated. Only **Learning Equality team members** can approve this request. Once approved, the deployment is executed, and visual tests are run automatically. The results of the test run are surfaced in the form of an automated comment containing a link to the Percy build.

**Note:** Developers outside of Learning Equality can only review the visual changes for local test runs. To review the visual tests executions that run on GitHub Actions, one needs to be a part of Learning Equality's Browserstack team.

### Writing visual tests

You can write the visual tests alongside the unit tests, i.e. in the same test file. Take a look at [`KButton.spec.js`](../lib/buttons-and-links/__tests__/KButton.spec.js) to familiarize yourself with writing visual tests.

1. **Import Utility Functions**:
- Import the utility functions from [`visual.testUtils.js`](../jest.conf/visual.testUtils.js):

```javascript
import { renderComponent, takeSnapshot } from './visual.testUtils';
```

You can import and use the utility functions for managing component's visual states as needed. Different utility functions available are:

- `renderComponent(component, props, slots)`: Renders the specified component with given props and slots in the visual testing playground.
- `takeSnapshot(name, options)`: Takes a Percy snapshot with the given name and options.
- `click(selector)`, `hover(selector)`, `scrollToPos(selector, scrollOptions)`, `waitFor(selector)`, `delay(time)`: Utility functions for simulating user interactions.

2. **Write Tests**:
- Use the utility functions to render components and take snapshots. For example:

```javascript
describe.visual('KButton Visual Tests', () => {
it('Sample test for KButton', async () => {
await renderComponent('KButton', { text: 'Raised Button', appearance: 'raised-button' });
await takeSnapshot('KButton - Raised Button', { widths: [375, 520] });
});
});
```
Note that the `widths` parameter passed to the `takeSnaphot` function is a part of Percy CLI's snapshot options. For a full list of available options, refer the [Percy documentation](https://www.browserstack.com/docs/percy/take-percy-snapshots/snapshots-via-scripts#per-snapshot-configuration).

- For rendering complex commponents, refer to the following:

- **Example with slots:** For components that involve slots, you can render them with `renderComponent` by passing the slot structure using element and elementProps. You can pass multiple slots at once.

```javascript
await renderComponent('KIconButton', { icon: 'add' }, {
menu: { // slot named #menu
element: 'KDropdownMenu',
elementProps: {
items: ['Option 1', 'Option 2'],
},
},
});
```

**Note:** Use `'default'` key for passing default slots, with the HTML content specified using `innerHTML` prop. Checkout [`KButton.spec.js`](../lib/buttons-and-links/__tests__/KButton.spec.js) for reference.

- **Example involving more complex component structures:** When dealing with more complex component structures, it's recommended to create a dedicated Vue component for visual testing purposes. Add all the use cases in a Vue file and then render the custom component using the `renderComponent` function.

```javascript
await renderComponent('CustomVueComponent');
```

This approach ensures that all necessary child components and slots are correctly set up and rendered.

- Make sure to use `describe.visual` or `it.visual` instead of the default notations for writing test blocks containing visual tests so as to prevent any unexpected behavior. These custom blocks add a `[Visual]` tag to the test name whose presence or absence are then checked using a regex pattern based on the type of tests executed.
- Anything inside these blocks will not be executed when running unit tests. The default `describe` and `it` blocks can be used inside a parent `describe.visual` block, which itelf can be placed within a `describe` block as its parent (as `describe` blocks just group the tests placed within them).
- In simple terms, any test block with a `[Visual]` tag will be executed when running visual tests, regardless of the type of test blocks used within it, and will be ignored when running unit tests. Using `describe.visual` or `it.visual` automatically appends this tag to the test name.
- This implementation helps determine which test blocks should be executed by Jest and which ones should be skipped.

3. **Simulate User Interactions**:
- Use the custom commands to simulate user interactions. For example, to simulate the *'click'* user event, you can do something like:

```javascript
await click('button');
```

Here, *'button'* is the CSS selector for the component. You can pass different selectors to the functions, exposed by [`visual.testUtils.js`](../jest.conf/visual.testUtils.js), to simulate user interaction as per requirement.

## Implementation details

The visual testing mechanism uses the following dependencies to ensure components render correctly under various conditions:

- **Puppeteer:** for interacting with the testing environment and the rendered components.
- **Jest-Puppeteer:** to provide all required configuration to run tests using Puppeteer.
- **Percy:** to take snapshots for comparing visual diffs.

The key parts of the mechanism include:

1. **Configuration Files**: Since we are using Jest for both unit and visual tests, there are two separate configuration files for visual tests apart from the ones being used for unit tests so as to ensure separation of logic needed for running both types of tests.
- [***visual.index.js***](../jest.conf/visual.index.js): Configures Jest-Puppeteer and includes server checks to ensure the visual testing playground is up and running.
- [***visual.setup.js***](../jest.conf/visual.setup.js): Sets up global functions and constants needed for visual tests.

2. **Utility Functions** [***(visual.testUtils.js)***](../jest.conf/visual.testUtils.js): We are also using a separate file that contains all the utility functions that are needed for writing visual tests.

3. **Visual Testing Playground** ([***testing-playground.vue***](../docs/pages/testing-playground.vue)):
- A dedicated page rendered by the devserver for component visual testing, ensuring expected visual behavior under various conditions.
- The visual test command runs the devserver and once the server is up and the testing playground page is loaded, the visual tests are executed and the required components are rendered dynamically based on messages received from the test runner.
Loading
Loading