feat: add E2E testing (#101)

Closes: #42
This commit is contained in:
Jacob
2022-12-07 09:00:57 +01:00
committed by GitHub
parent e83730e6ea
commit 6398c54e4c
23 changed files with 3547 additions and 262 deletions

View File

@@ -16,6 +16,7 @@ jobs:
node-version: 18
cache: npm
- run: npm ci
- run: npm run examples:build
- run: npm run docs:build
- uses: peaceiris/actions-gh-pages@v3
with:

View File

@@ -29,3 +29,5 @@ jobs:
run: npx lerna run test
- name: Build
run: npx lerna run build
- name: E2E
run: npm run e2e:test

View File

@@ -10,6 +10,7 @@ module.exports = {
'core',
'create',
'docs',
'e2e',
'examples',
'legacy',
'player',

3648
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@
"docs:build": "npm run build -w packages/docs",
"vite-plugin:build": "npm run build -w packages/vite-plugin",
"vite-plugin:watch": "npm run watch -w packages/vite-plugin",
"e2e:test": "npm run test -w packages/e2e",
"eslint": "eslint --fix \"**/*.ts?(x)\"",
"prettier": "prettier --write ."
},

View File

@@ -363,6 +363,12 @@ export class Project {
}
}
public async finishExport() {
while (this.exportCounter > 0) {
await new Promise(resolve => setTimeout(resolve, EXPORT_RETRY_DELAY));
}
}
public syncAudio(frameOffset = 0) {
this.audio.setTime(this.framesToSeconds(this.frame + frameOffset));
}

View File

@@ -293,6 +293,7 @@ export class Player {
}
if (state.finished || this.project.frame >= state.endFrame) {
await this.project.finishExport();
this.toggleRendering(false);
}

2
packages/e2e/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
output
src/__image_snapshots__/__diff_output__

22
packages/e2e/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "@motion-canvas/e2e",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"test": "vitest"
},
"dependencies": {
"@motion-canvas/2d": "*",
"@motion-canvas/core": "*",
"jest-image-snapshot": "^6.1.0",
"puppeteer": "^19.3.0",
"vitest": "^0.25.5"
},
"devDependencies": {
"@motion-canvas/ui": "*",
"@motion-canvas/vite-plugin": "*",
"@types/puppeteer": "^7.0.4",
"vite": "^3.0.5"
}
}

34
packages/e2e/src/app.ts Normal file
View File

@@ -0,0 +1,34 @@
import * as path from 'path';
import puppeteer, {Page} from 'puppeteer';
import {fileURLToPath} from 'url';
import {createServer} from 'vite';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export interface App {
page: Page;
stop: () => Promise<void>;
}
export async function start(): Promise<App> {
const [browser, server] = await Promise.all([
puppeteer.launch(),
createServer({
root: __dirname,
configFile: path.resolve(__dirname, '../vite.config.ts'),
server: {
port: 9000,
},
}).then(server => server.listen()),
]);
const page = await browser.newPage();
await page.goto('http://localhost:9000');
return {
page,
async stop() {
await Promise.all([browser.close(), server.close()]);
},
};
}

10
packages/e2e/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
declare namespace jest {
interface Matchers<R> {
toMatchImageSnapshot(): R;
}
}
declare module 'jest-image-snapshot' {
const toMatchImageSnapshot: any;
export {toMatchImageSnapshot};
}

View File

@@ -0,0 +1,26 @@
import {afterAll, beforeAll, describe, expect, test} from 'vitest';
import {toMatchImageSnapshot} from 'jest-image-snapshot';
import {App, start} from './app';
import {readFileSync} from 'fs';
expect.extend({toMatchImageSnapshot});
describe('Rendering', () => {
let app: App;
beforeAll(async () => {
app = await start();
});
afterAll(async () => {
await app.stop();
});
test('Animation renders correctly', async () => {
await app.page.click('#rendering-tab');
await app.page.click('#render');
await app.page.waitForSelector('#render:not([data-rendering])');
expect(readFileSync('./output/project/000300.png')).toMatchImageSnapshot();
});
});

View File

@@ -0,0 +1,7 @@
{
"extends": "@motion-canvas/2d/tsconfig.project.json",
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}

View File

@@ -0,0 +1,15 @@
/// <reference types="vitest" />
import {defineConfig} from 'vite';
import motionCanvas from '@motion-canvas/vite-plugin';
export default defineConfig({
plugins: [
motionCanvas.default({
project: ['@motion-canvas/template/src/project.ts'],
}),
],
test: {
testTimeout: 60000,
},
});

View File

@@ -7,8 +7,8 @@
"build": "tsc && vite build"
},
"dependencies": {
"@motion-canvas/core": "*",
"@motion-canvas/2d": "*"
"@motion-canvas/2d": "*",
"@motion-canvas/core": "*"
},
"devDependencies": {
"@motion-canvas/ui": "*",

View File

@@ -21,7 +21,7 @@ export function Console() {
const [filters, setFilters] = useState(LOG_LEVELS);
return (
<Pane title="Console">
<Pane title="Console" id="console">
<div className={styles.navbar}>
<div className={styles.pills}>
{Object.keys(LOG_LEVELS).map(level => {

View File

@@ -22,7 +22,7 @@ export function Properties() {
);
return (
<Pane title="Properties">
<Pane title="Properties" id="properties-pane">
{attributes
? Object.entries(attributes).map(([key, value]) => (
<AutoField label={key} value={value} />

View File

@@ -46,7 +46,7 @@ export function Rendering() {
];
return (
<Pane title="Rendering">
<Pane title="Rendering" id="rendering-pane">
<Group>
<Label>range</Label>
<Input
@@ -148,7 +148,12 @@ export function Rendering() {
)}
<Group>
<Label />
<Button main onClick={() => player.toggleRendering()}>
<Button
main
id="render"
data-rendering={state.render}
onClick={() => player.toggleRendering()}
>
{state.render ? 'STOP RENDERING' : 'RENDER'}
</Button>
</Group>

View File

@@ -47,6 +47,7 @@ export function Sidebar({setOpen}: SidebarProps) {
>
{{
title: 'Project Selection',
id: 'project-selection-link',
type: TabType.Link,
icon: IconType.motionCanvas,
url: window.location.pathname === '/' ? undefined : '/',
@@ -56,24 +57,28 @@ export function Sidebar({setOpen}: SidebarProps) {
}}
{{
title: 'Inspector',
id: 'inspector-tab',
type: TabType.Pane,
icon: IconType.tune,
pane: <Properties />,
}}
{{
title: 'Video Settings',
id: 'rendering-tab',
type: TabType.Pane,
icon: IconType.videoSettings,
pane: <Rendering />,
}}
{{
title: 'Thread Debugger',
id: 'threads-tab',
type: TabType.Pane,
icon: IconType.schedule,
pane: <Threads />,
}}
{{
title: errorCount > 0 ? `Console (${errorCount})` : 'Console',
id: 'console-tab',
type: TabType.Pane,
icon: IconType.bug,
pane: <Console />,

View File

@@ -18,7 +18,7 @@ export function Threads() {
);
return (
<Pane title="Threads">
<Pane title="Threads" id="threads-pane">
{thread ? (
<ThreadView thread={thread} />
) : (

View File

@@ -4,12 +4,13 @@ import {ComponentChildren} from 'preact';
export interface PaneProps {
title: string;
id?: string;
children: ComponentChildren;
}
export function Pane({title, children}: PaneProps) {
export function Pane({title, id, children}: PaneProps) {
return (
<div className={styles.pane}>
<div className={styles.pane} id={id}>
<div className={styles.header}>{title}</div>
{children}
</div>

View File

@@ -18,12 +18,14 @@ type Tab =
pane: ComponentChildren;
badge?: ComponentChildren;
title?: string;
id?: string;
}
| {
type: TabType.Link;
icon: IconType;
url?: string;
title?: string;
id?: string;
}
| {
type: TabType.Space;
@@ -62,12 +64,14 @@ export function Tabs({children, tab, onToggle}: TabsProps) {
title={data.title}
type={data.icon}
href={data.url}
id={data.id}
className={classes(styles.tab, [styles.disabled, !data.url])}
/>
) : data.type === TabType.Pane ? (
<Icon
type={data.icon}
title={data.title}
id={data.id}
onClick={() => toggleTab(index)}
className={classes(styles.tab, [styles.active, tab === index])}
>