Merge branch 'main' into hosted
This commit is contained in:
commit
558e8634eb
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [abi]
|
||||
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots of backend AND frontend terminal logs**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@ -31,6 +31,11 @@ We also just added experimental support for taking a video/screen recording of a
|
||||
|
||||
[Follow me on Twitter for updates](https://twitter.com/_abi_).
|
||||
|
||||
## Sponsors
|
||||
|
||||
<a href="https://platform.sh/try-it-now/?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" target="_blank" title="Build, run and scale your apps on a modern, reliable, and secure PaaS."><img src="https://fastapi.tiangolo.com/img/sponsors/platform-sh.png"></a>
|
||||
|
||||
|
||||
## 🚀 Try It Out without no install
|
||||
|
||||
[Try it live on the hosted version (paid)](https://screenshottocode.com).
|
||||
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@ -25,3 +25,6 @@ dist-ssr
|
||||
|
||||
# Env files
|
||||
.env*
|
||||
|
||||
# Test files
|
||||
src/tests/results/
|
||||
|
||||
9
frontend/jest.config.js
Normal file
9
frontend/jest.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
setupFiles: ["<rootDir>/src/setupTests.ts"],
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "ts-jest",
|
||||
},
|
||||
testTimeout: 30000,
|
||||
};
|
||||
@ -10,7 +10,7 @@
|
||||
"build-hosted": "tsc && vite build --mode prod",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest"
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^4.29.0",
|
||||
@ -57,18 +57,24 @@
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/puppeteer": "^7.0.4",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"jest": "^29.7.0",
|
||||
"postcss": "^8.4.31",
|
||||
"puppeteer": "^22.6.4",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
|
||||
2
frontend/src/.env.jest.example
Normal file
2
frontend/src/.env.jest.example
Normal file
@ -0,0 +1,2 @@
|
||||
TEST_SCREENSHOTONE_API_KEY=
|
||||
TEST_ROOT_PATH=
|
||||
@ -516,7 +516,7 @@ function App({ navbarComponent }: Props) {
|
||||
</div>
|
||||
<Button
|
||||
onClick={doUpdate}
|
||||
className="dark:text-white dark:bg-gray-700 plausible-event-name=Edit"
|
||||
className="dark:text-white dark:bg-gray-700 plausible-event-name=Edit update-btn"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
@ -524,7 +524,7 @@ function App({ navbarComponent }: Props) {
|
||||
<div className="flex items-center justify-end gap-x-2 mt-2">
|
||||
<Button
|
||||
onClick={regenerate}
|
||||
className="flex items-center gap-x-2 dark:text-white dark:bg-gray-700"
|
||||
className="flex items-center gap-x-2 dark:text-white dark:bg-gray-700 regenerate-btn"
|
||||
>
|
||||
🔄 Regenerate
|
||||
</Button>
|
||||
@ -635,7 +635,7 @@ function App({ navbarComponent }: Props) {
|
||||
<Button
|
||||
onClick={downloadCode}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-x-2 mr-4 dark:text-white dark:bg-gray-700"
|
||||
className="flex items-center gap-x-2 mr-4 dark:text-white dark:bg-gray-700 download-btn"
|
||||
>
|
||||
<FaDownload /> Download
|
||||
</Button>
|
||||
|
||||
@ -179,7 +179,7 @@ function ImageUpload({ setReferenceImages }: Props) {
|
||||
{screenRecorderState === ScreenRecorderState.INITIAL && (
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
<div {...getRootProps({ style: style as any })}>
|
||||
<input {...getInputProps()} />
|
||||
<input {...getInputProps()} className="file-input" />
|
||||
<p className="text-slate-700 text-lg">
|
||||
Drag & drop a screenshot here, <br />
|
||||
or click to upload
|
||||
|
||||
@ -38,7 +38,9 @@ function ImportCodeSection({ importFromCode }: Props) {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary">Import from Code</Button>
|
||||
<Button className="import-from-code-btn" variant="secondary">
|
||||
Import from Code
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
@ -62,7 +64,7 @@ function ImportCodeSection({ importFromCode }: Props) {
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="submit" onClick={doImport}>
|
||||
<Button className="import-btn" type="submit" onClick={doImport}>
|
||||
Import
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@ -74,7 +74,7 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) {
|
||||
<Button
|
||||
onClick={takeScreenshot}
|
||||
disabled={isLoading}
|
||||
className="bg-slate-400"
|
||||
className="bg-slate-400 capture-btn"
|
||||
>
|
||||
{isLoading ? "Capturing..." : "Capture"}
|
||||
</Button>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { extractHistoryTree, renderHistory } from "./utils";
|
||||
import type { History } from "./history_types";
|
||||
|
||||
@ -84,147 +83,149 @@ const basicBadHistory: History = [
|
||||
},
|
||||
];
|
||||
|
||||
test("should correctly extract the history tree", () => {
|
||||
expect(extractHistoryTree(basicLinearHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
describe("History Utils", () => {
|
||||
test("should correctly extract the history tree", () => {
|
||||
expect(extractHistoryTree(basicLinearHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
|
||||
expect(extractHistoryTree(basicLinearHistory, 0)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
]);
|
||||
expect(extractHistoryTree(basicLinearHistory, 0)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
]);
|
||||
|
||||
// Test branching
|
||||
expect(extractHistoryTree(basicBranchingHistory, 3)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
]);
|
||||
// Test branching
|
||||
expect(extractHistoryTree(basicBranchingHistory, 3)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
]);
|
||||
|
||||
expect(extractHistoryTree(longerBranchingHistory, 4)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
"make text bold",
|
||||
"<html>5. edit with better icons and green, bold text</html>",
|
||||
]);
|
||||
expect(extractHistoryTree(longerBranchingHistory, 4)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text green",
|
||||
"<html>4. edit with better icons and green text</html>",
|
||||
"make text bold",
|
||||
"<html>5. edit with better icons and green, bold text</html>",
|
||||
]);
|
||||
|
||||
expect(extractHistoryTree(longerBranchingHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
expect(extractHistoryTree(longerBranchingHistory, 2)).toEqual([
|
||||
"<html>1. create</html>",
|
||||
"use better icons",
|
||||
"<html>2. edit with better icons</html>",
|
||||
"make text red",
|
||||
"<html>3. edit with better icons and red text</html>",
|
||||
]);
|
||||
|
||||
// Errors
|
||||
// Errors
|
||||
|
||||
// Bad index
|
||||
expect(() => extractHistoryTree(basicLinearHistory, 100)).toThrow();
|
||||
expect(() => extractHistoryTree(basicLinearHistory, -2)).toThrow();
|
||||
// Bad index
|
||||
expect(() => extractHistoryTree(basicLinearHistory, 100)).toThrow();
|
||||
expect(() => extractHistoryTree(basicLinearHistory, -2)).toThrow();
|
||||
|
||||
// Bad tree
|
||||
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
||||
});
|
||||
|
||||
test("should correctly render the history tree", () => {
|
||||
expect(renderHistory(basicLinearHistory, 2)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Current version is the first version
|
||||
expect(renderHistory(basicLinearHistory, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a history with code
|
||||
expect(renderHistory(basicLinearHistoryWithCode, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Imported from code",
|
||||
type: "Imported from code",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a non-linear history
|
||||
expect(renderHistory(basicBranchingHistory, 3)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: "v2",
|
||||
summary: "make text green",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
// Bad tree
|
||||
expect(() => extractHistoryTree(basicBadHistory, 1)).toThrow();
|
||||
});
|
||||
|
||||
test("should correctly render the history tree", () => {
|
||||
expect(renderHistory(basicLinearHistory, 2)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Current version is the first version
|
||||
expect(renderHistory(basicLinearHistory, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a history with code
|
||||
expect(renderHistory(basicLinearHistoryWithCode, 0)).toEqual([
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: null,
|
||||
summary: "Imported from code",
|
||||
type: "Imported from code",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
|
||||
// Render a non-linear history
|
||||
expect(renderHistory(basicBranchingHistory, 3)).toEqual([
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "Create",
|
||||
type: "Create",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "use better icons",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: false,
|
||||
parentVersion: null,
|
||||
summary: "make text red",
|
||||
type: "Edit",
|
||||
},
|
||||
{
|
||||
isActive: true,
|
||||
parentVersion: "v2",
|
||||
summary: "make text green",
|
||||
type: "Edit",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
3
frontend/src/setupTests.ts
Normal file
3
frontend/src/setupTests.ts
Normal file
@ -0,0 +1,3 @@
|
||||
// So jest test runner can read env vars from .env file
|
||||
import { config } from "dotenv";
|
||||
config({ path: ".env.jest" });
|
||||
BIN
frontend/src/tests/fixtures/simple_button.png
vendored
Normal file
BIN
frontend/src/tests/fixtures/simple_button.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
frontend/src/tests/fixtures/simple_ui_with_image.png
vendored
Normal file
BIN
frontend/src/tests/fixtures/simple_ui_with_image.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
274
frontend/src/tests/qa.test.ts
Normal file
274
frontend/src/tests/qa.test.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import puppeteer, { Browser, Page, ElementHandle } from "puppeteer";
|
||||
import { Stack } from "../lib/stacks";
|
||||
import { CodeGenerationModel } from "../lib/models";
|
||||
|
||||
const TESTS_ROOT_PATH = process.env.TEST_ROOT_PATH;
|
||||
|
||||
// Fixtures
|
||||
const FIXTURES_PATH = `${TESTS_ROOT_PATH}/fixtures`;
|
||||
const SIMPLE_SCREENSHOT = FIXTURES_PATH + "/simple_button.png";
|
||||
const SCREENSHOT_WITH_IMAGES = `${FIXTURES_PATH}/simple_ui_with_image.png`;
|
||||
|
||||
// Results
|
||||
const RESULTS_DIR = `${TESTS_ROOT_PATH}/results`;
|
||||
|
||||
describe("e2e tests", () => {
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
|
||||
const DEBUG = false;
|
||||
const IS_HEADLESS = true;
|
||||
|
||||
const stacks = Object.values(Stack).slice(0, DEBUG ? 1 : undefined);
|
||||
const models = Object.values(CodeGenerationModel).slice(
|
||||
0,
|
||||
DEBUG ? 1 : undefined
|
||||
);
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await puppeteer.launch({ headless: IS_HEADLESS });
|
||||
page = await browser.newPage();
|
||||
await page.goto("http://localhost:5173/");
|
||||
|
||||
// Set screen size
|
||||
await page.setViewport({ width: 1080, height: 1024 });
|
||||
|
||||
// TODO: Does this need to be moved?
|
||||
// const client = await page.createCDPSession();
|
||||
// Set download behavior path
|
||||
// await client.send("Page.setDownloadBehavior", {
|
||||
// behavior: "allow",
|
||||
// downloadPath: DOWNLOAD_PATH,
|
||||
// });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
// Create tests
|
||||
models.forEach((model) => {
|
||||
stacks.forEach((stack) => {
|
||||
it(
|
||||
`Create for : ${model} & ${stack}`,
|
||||
async () => {
|
||||
const app = new App(
|
||||
page,
|
||||
stack,
|
||||
model,
|
||||
`create_screenshot_${model}_${stack}`
|
||||
);
|
||||
await app.init();
|
||||
// Generate from screenshot
|
||||
await app.uploadImage(SCREENSHOT_WITH_IMAGES);
|
||||
},
|
||||
60 * 1000
|
||||
);
|
||||
|
||||
it(
|
||||
`Create from URL for : ${model} & ${stack}`,
|
||||
async () => {
|
||||
const app = new App(
|
||||
page,
|
||||
stack,
|
||||
model,
|
||||
`create_url_${model}_${stack}`
|
||||
);
|
||||
await app.init();
|
||||
// Generate from screenshot
|
||||
await app.generateFromUrl("https://a.picoapps.xyz/design-fear");
|
||||
},
|
||||
60 * 1000
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Update tests - for every model (doesn’t need to be repeated for each stack - fix to HTML Tailwind only)
|
||||
models.forEach((model) => {
|
||||
["html_tailwind"].forEach((stack) => {
|
||||
it(
|
||||
`update: ${model}`,
|
||||
async () => {
|
||||
const app = new App(page, stack, model, `update_${model}_${stack}`);
|
||||
await app.init();
|
||||
|
||||
// Generate from screenshot
|
||||
await app.uploadImage(SIMPLE_SCREENSHOT);
|
||||
// Regenerate works for v1
|
||||
await app.regenerate();
|
||||
// Make an update
|
||||
await app.edit("make the button background blue", "v2");
|
||||
// Make another update
|
||||
await app.edit("make the text italic", "v3");
|
||||
// Branch off v2 and make an update
|
||||
await app.clickVersion("v2");
|
||||
await app.edit("make the text yellow", "v4");
|
||||
},
|
||||
90 * 1000
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Start from code tests - for every model
|
||||
models.forEach((model) => {
|
||||
["html_tailwind"].forEach((stack) => {
|
||||
it.skip(
|
||||
`Start from code: ${model}`,
|
||||
async () => {
|
||||
const app = new App(
|
||||
page,
|
||||
stack,
|
||||
model,
|
||||
`start_from_code_${model}_${stack}`
|
||||
);
|
||||
await app.init();
|
||||
|
||||
await app.importFromCode();
|
||||
|
||||
// Regenerate works for v1
|
||||
// await app.regenerate();
|
||||
// // Make an update
|
||||
// await app.edit("make the header blue", "v2");
|
||||
// // Make another update
|
||||
// await app.edit("make all text italic", "v3");
|
||||
// // Branch off v2 and make an update
|
||||
// await app.clickVersion("v2");
|
||||
// await app.edit("make all text red", "v4");
|
||||
},
|
||||
90 * 1000
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class App {
|
||||
private screenshotPathPrefix: string;
|
||||
private page: Page;
|
||||
private stack: string;
|
||||
private model: string;
|
||||
|
||||
constructor(page: Page, stack: string, model: string, testId: string) {
|
||||
this.page = page;
|
||||
this.stack = stack;
|
||||
this.model = model;
|
||||
this.screenshotPathPrefix = `${RESULTS_DIR}/${testId}`;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.setupLocalStorage();
|
||||
}
|
||||
|
||||
async setupLocalStorage() {
|
||||
const setting = {
|
||||
openAiApiKey: null,
|
||||
openAiBaseURL: null,
|
||||
screenshotOneApiKey: process.env.TEST_SCREENSHOTONE_API_KEY,
|
||||
isImageGenerationEnabled: true,
|
||||
editorTheme: "cobalt",
|
||||
generatedCodeConfig: this.stack,
|
||||
codeGenerationModel: this.model,
|
||||
isTermOfServiceAccepted: false,
|
||||
accessCode: null,
|
||||
};
|
||||
|
||||
await this.page.evaluate((setting) => {
|
||||
localStorage.setItem("setting", JSON.stringify(setting));
|
||||
}, setting);
|
||||
|
||||
// Reload the page to apply the local storage
|
||||
await this.page.reload();
|
||||
}
|
||||
|
||||
async _screenshot(step: string) {
|
||||
await this.page.screenshot({
|
||||
path: `${this.screenshotPathPrefix}_${step}.png`,
|
||||
});
|
||||
}
|
||||
|
||||
async _waitUntilVersionIsReady(version: string) {
|
||||
await this.page.waitForNetworkIdle();
|
||||
await this.page.waitForFunction(
|
||||
(version) => document.body.innerText.includes(version),
|
||||
{
|
||||
timeout: 30000,
|
||||
},
|
||||
version
|
||||
);
|
||||
// Wait for 3s so that the HTML and JS has time to render before screenshotting
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
}
|
||||
|
||||
async generateFromUrl(url: string) {
|
||||
// Type in the URL
|
||||
await this.page.type('input[placeholder="Enter URL"]', url);
|
||||
await this._screenshot("typed_url");
|
||||
|
||||
// Click the capture button and wait for the code to be generated
|
||||
await this.page.click("button.capture-btn");
|
||||
await this._waitUntilVersionIsReady("v1");
|
||||
await this._screenshot("url_result");
|
||||
}
|
||||
|
||||
// Uploads a screenshot and generates the image
|
||||
async uploadImage(screenshotPath: string) {
|
||||
// Upload file
|
||||
const fileInput = (await this.page.$(
|
||||
".file-input"
|
||||
)) as ElementHandle<HTMLInputElement>;
|
||||
if (!fileInput) {
|
||||
throw new Error("File input element not found");
|
||||
}
|
||||
await fileInput.uploadFile(screenshotPath);
|
||||
await this._screenshot("image_uploaded");
|
||||
|
||||
// Click the generate button and wait for the code to be generated
|
||||
await this._waitUntilVersionIsReady("v1");
|
||||
await this._screenshot("image_results");
|
||||
}
|
||||
|
||||
// Makes a text edit and waits for a new version
|
||||
async edit(edit: string, version: string) {
|
||||
// Type in the edit
|
||||
await this.page.type(
|
||||
'textarea[placeholder="Tell the AI what to change..."]',
|
||||
edit
|
||||
);
|
||||
await this._screenshot(`typed_${version}`);
|
||||
|
||||
// Click the update button and wait for the code to be generated
|
||||
await this.page.click(".update-btn");
|
||||
await this._waitUntilVersionIsReady(version);
|
||||
await this._screenshot(`done_${version}`);
|
||||
}
|
||||
|
||||
async clickVersion(version: string) {
|
||||
await this.page.evaluate((version) => {
|
||||
document.querySelectorAll("div").forEach((div) => {
|
||||
if (div.innerText.includes(version)) {
|
||||
div.click();
|
||||
}
|
||||
});
|
||||
}, version);
|
||||
}
|
||||
|
||||
async regenerate() {
|
||||
await this.page.click(".regenerate-btn");
|
||||
await this._waitUntilVersionIsReady("v1");
|
||||
await this._screenshot("regenerate_results");
|
||||
}
|
||||
|
||||
// Work in progress
|
||||
async importFromCode() {
|
||||
await this.page.click(".import-from-code-btn");
|
||||
|
||||
await this.page.type("textarea", "<html>hello world</html>");
|
||||
|
||||
await this.page.select("#output-settings-js", "HTML + Tailwind");
|
||||
|
||||
await this._screenshot("typed_code");
|
||||
|
||||
await this.page.click(".import-btn");
|
||||
|
||||
await this._waitUntilVersionIsReady("v1");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user