Merge pull request #266 from othrayte/website-e2e-tests

Website e2e tests
This commit is contained in:
Keith Stevens
2023-01-02 19:43:49 +09:00
committed by GitHub
20 changed files with 287 additions and 21 deletions
+7
View File
@@ -22,4 +22,11 @@ export default defineConfig({
getCompareSnapshotsPlugin(on, config);
},
},
env: {
MAILDEV_PROTOCOL: "http",
MAILDEV_HOST: "localhost",
MAILDEV_SMTP_PORT: "1025",
MAILDEV_API_PORT: "1080",
},
});
+33 -2
View File
@@ -1,10 +1,41 @@
import { faker } from "@faker-js/faker";
describe("signin flow", () => {
it("redirects to a confirmation page on submit of valid email address", () => {
cy.visit("/auth/signin");
cy.get(".chakra-input").type(`test@example.com`);
cy.get(".chakra-stack > .chakra-button").click();
cy.get('form[data-cy="signin-email"').within(() => {
cy.get(".chakra-input").type(`test@example.com`);
cy.get(".chakra-stack > .chakra-button").click();
});
cy.url().should("contain", "/auth/verify");
});
it("emails a login link to the user when signing in with email", () => {
// Use random email to avoid possibility of tests passing just due to other tests or previous runs also causing emails to be sent
const emailAddress = faker.internet.email();
cy.log("emailAddress", emailAddress);
cy.request("GET", "/api/auth/csrf")
.then((response) => {
const csrfToken = response.body.csrfToken;
cy.request("POST", "/api/auth/signin/email", {
callbackUrl: "/",
email: emailAddress,
csrfToken,
json: "true",
});
})
.then((response) => {
cy.signInUsingEmailedLink(emailAddress).then(() => {
cy.get('[data-cy="username"]').should("exist");
});
});
});
it("shows the logged in users email address if logged in with email", () => {
const emailAddress = "user@example.com";
cy.signInWithEmail(emailAddress);
// The user will only see the email address if the window is wide enough, not technically required as even when hidden this will find it in the page.
cy.viewport(1920, 1000);
cy.contains('[data-cy="username"]', emailAddress);
});
});
export {};
@@ -0,0 +1,26 @@
import { faker } from "@faker-js/faker";
describe("replying as the assistant", () => {
it("completes the current task on submit and on request shows a new task", () => {
cy.signInWithEmail("cypress@example.com");
cy.visit("/create/assistant_reply");
cy.get('[data-cy="task-id"').then((taskIdElement) => {
const taskId = taskIdElement.text();
const reply = faker.lorem.sentence();
cy.log("reply", reply);
cy.get('[data-cy="reply"').type(reply);
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="next-task"]').click();
cy.get('[data-cy="task-id"').should((taskIdElement) => {
expect(taskIdElement.text()).not.to.eq(taskId);
});
});
});
});
export {};
@@ -0,0 +1,26 @@
import { faker } from "@faker-js/faker";
describe("replying as the prompter", () => {
it("completes the current task on submit and on request shows a new task", () => {
cy.signInWithEmail("cypress@example.com");
cy.visit("/create/user_reply");
cy.get('[data-cy="task-id"').then((taskIdElement) => {
const taskId = taskIdElement.text();
const reply = faker.lorem.sentence();
cy.log("reply", reply);
cy.get('[data-cy="reply"').type(reply);
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="next-task"]').click();
cy.get('[data-cy="task-id"').should((taskIdElement) => {
expect(taskIdElement.text()).not.to.eq(taskId);
});
});
});
});
export {};
@@ -0,0 +1,30 @@
describe("ranking prompter replies", () => {
it("completes the current task on submit and on request shows a new task", () => {
cy.signInWithEmail("cypress@example.com");
cy.visit("/evaluate/rank_user_replies");
cy.get('[data-cy="task-id"').then((taskIdElement) => {
const taskId = taskIdElement.text();
// Rank an item using the keyboard so that the submit button is enabled
cy.get('button[aria-roledescription="sortable"]')
.first()
.click()
.type("{enter}")
.wait(100)
.type("{downArrow}")
.wait(100)
.type("{enter}");
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="next-task"]').click();
cy.get('[data-cy="task-id"').should((taskIdElement) => {
expect(taskIdElement.text()).not.to.eq(taskId);
});
});
});
});
export {};
@@ -0,0 +1,30 @@
describe("ranking initial prompts", () => {
it("completes the current task on submit and on request shows a new task", () => {
cy.signInWithEmail("cypress@example.com");
cy.visit("/evaluate/rank_initial_prompts");
cy.get('[data-cy="task-id"').then((taskIdElement) => {
const taskId = taskIdElement.text();
// Rank an item using the keyboard so that the submit button is enabled
cy.get('button[aria-roledescription="sortable"]')
.first()
.click()
.type("{enter}")
.wait(100)
.type("{downArrow}")
.wait(100)
.type("{enter}");
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="next-task"]').click();
cy.get('[data-cy="task-id"').should((taskIdElement) => {
expect(taskIdElement.text()).not.to.eq(taskId);
});
});
});
});
export {};
@@ -0,0 +1,30 @@
describe("ranking assistant replies", () => {
it("completes the current task on submit and on request shows a new task", () => {
cy.signInWithEmail("cypress@example.com");
cy.visit("/evaluate/rank_assistant_replies");
cy.get('[data-cy="task-id"').then((taskIdElement) => {
const taskId = taskIdElement.text();
// Rank an item using the keyboard so that the submit button is enabled
cy.get('button[aria-roledescription="sortable"]')
.first()
.click()
.type("{enter}")
.wait(100)
.type("{downArrow}")
.wait(100)
.type("{enter}");
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="next-task"]').click();
cy.get('[data-cy="task-id"').should((taskIdElement) => {
expect(taskIdElement.text()).not.to.eq(taskId);
});
});
});
});
export {};
+34
View File
@@ -36,4 +36,38 @@
// }
// }
Cypress.Commands.add("signInUsingEmailedLink", (emailAddress) => {
const mailDevApi = `${Cypress.env("MAILDEV_PROTOCOL")}://${Cypress.env(
"MAILDEV_HOST"
)}:${Cypress.env("MAILDEV_API_PORT")}`;
cy.request(
"GET",
`${mailDevApi}/email?headers.to=${emailAddress.toLowerCase()}`
).then((response) => {
const emails = response.body;
// Find and use login link
const loginLink = emails
.pop()
.html.match(/href="[^"]+(\/api\/auth\/callback\/[^"]+?)"/)[1];
cy.visit(loginLink);
});
});
Cypress.Commands.add("signInWithEmail", (emailAddress) => {
cy.request("GET", "/api/auth/csrf")
.then((response) => {
const csrfToken = response.body.csrfToken;
cy.request("POST", "/api/auth/signin/email", {
callbackUrl: "/",
email: emailAddress,
csrfToken,
json: "true",
});
})
.then(() => {
cy.signInUsingEmailedLink(emailAddress);
});
});
export {};
-4
View File
@@ -13,12 +13,8 @@
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
import compareSnapshotCommand from "cypress-image-diff-js/dist/command";
compareSnapshotCommand();
// Alternatively you can use CommonJS syntax:
// require('./commands')
export {};
+22
View File
@@ -0,0 +1,22 @@
// load type definitions that come with Cypress module
/// <reference types="cypress" />
declare global {
namespace Cypress {
interface Chainable {
/**
* Custom command to sign in with a given email address
* @example cy.signInWithEmail('user@example.com')
*/
signInWithEmail(emailAddress: string): Chainable<Element>;
/**
* Custom command to sign in with the link emailed to the given email address
* @example cy.signInUsingEmailedLink('user@example.com')
*/
signInUsingEmailedLink(emailAddress: string): Chainable<Element>;
}
}
}
export {};
+17
View File
@@ -44,6 +44,7 @@
"devDependencies": {
"@babel/core": "^7.20.7",
"@chakra-ui/storybook-addon": "^4.0.16",
"@faker-js/faker": "^7.6.0",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-interactions": "^6.5.15",
@@ -3591,6 +3592,16 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@faker-js/faker": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
"dev": true,
"engines": {
"node": ">=14.0.0",
"npm": ">=6.0.0"
}
},
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -31099,6 +31110,12 @@
}
}
},
"@faker-js/faker": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
"dev": true
},
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
+1
View File
@@ -54,6 +54,7 @@
"devDependencies": {
"@babel/core": "^7.20.7",
"@chakra-ui/storybook-addon": "^4.0.16",
"@faker-js/faker": "^7.6.0",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-interactions": "^6.5.15",
+3 -1
View File
@@ -34,7 +34,9 @@ export function UserMenu() {
height="40"
className="rounded-full"
></Image>
<p className="hidden lg:flex">{session.user.name || session.user.email}</p>
<p data-cy="username" className="hidden lg:flex">
{session.user.name || session.user.email}
</p>
</div>
</Popover.Button>
<AnimatePresence initial={false}>
+1 -1
View File
@@ -2,7 +2,7 @@ export const TaskInfo = ({ id, output }: { id: string; output: string }) => {
return (
<div className="grid grid-cols-[min-content_auto] gap-x-2 text-gray-700">
<b>Prompt</b>
<span>{id}</span>
<span data-cy="task-id">{id}</span>
<b>Output</b>
<span>{output}</span>
</div>
+1 -1
View File
@@ -41,7 +41,7 @@ export default function Signin({ csrfToken, providers }) {
</form>
)}
{email && (
<form onSubmit={signinWithEmail}>
<form data-cy="signin-email" onSubmit={signinWithEmail}>
<Stack>
<Input variant="outline" size="lg" placeholder="Email Address" ref={emailEl} />
<Button size={"lg"} leftIcon={<FaEnvelope />} colorScheme="gray" type="submit">
+7 -3
View File
@@ -63,7 +63,7 @@ const AssistantReply = () => {
<p className="text-lg py-1">Given the following conversation, provide an adequate reply</p>
<Messages messages={task.conversation.messages} post_id={task.id} />
</>
<Textarea name="reply" placeholder="Reply..." ref={inputRef} />
<Textarea name="reply" data-cy="reply" placeholder="Reply..." ref={inputRef} />
</TwoColumns>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
@@ -72,9 +72,13 @@ const AssistantReply = () => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
<SubmitButton data-cy="submit" onClick={() => submitResponse(tasks[0])}>
Submit
</SubmitButton>
) : (
<SubmitButton onClick={fetchNextTask}>Next Task</SubmitButton>
<SubmitButton data-cy="next-task" onClick={fetchNextTask}>
Next Task
</SubmitButton>
)}
</Flex>
</section>
+7 -3
View File
@@ -64,7 +64,7 @@ const UserReply = () => {
<Messages messages={task.conversation.messages} post_id={task.id} />
{task.hint && <p className="text-lg py-1">Hint: {task.hint}</p>}
</>
<Textarea name="reply" placeholder="Reply..." ref={inputRef} />
<Textarea name="reply" data-cy="reply" placeholder="Reply..." ref={inputRef} />
</TwoColumns>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
@@ -72,9 +72,13 @@ const UserReply = () => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
<SubmitButton data-cy="submit" onClick={() => submitResponse(tasks[0])}>
Submit
</SubmitButton>
) : (
<SubmitButton onClick={fetchNextTask}>Next Task</SubmitButton>
<SubmitButton data-cy="next-task" onClick={fetchNextTask}>
Next Task
</SubmitButton>
)}
</Flex>
</section>
@@ -78,11 +78,13 @@ const RankAssistantReplies = () => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
<SubmitButton data-cy="submit" onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</SubmitButton>
) : (
<SubmitButton onClick={fetchNextTask}>Next Task</SubmitButton>
<SubmitButton data-cy="next-task" onClick={fetchNextTask}>
Next Task
</SubmitButton>
)}
</Flex>
</section>
@@ -77,11 +77,13 @@ const RankInitialPrompts = () => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
<SubmitButton data-cy="submit" onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</SubmitButton>
) : (
<SubmitButton onClick={fetchNextTask}>Next Task</SubmitButton>
<SubmitButton data-cy="next-task" onClick={fetchNextTask}>
Next Task
</SubmitButton>
)}
</Flex>
</section>
@@ -78,11 +78,13 @@ const RankUserReplies = () => {
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
{endTask.task.type !== "task_done" ? (
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
<SubmitButton data-cy="submit" onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</SubmitButton>
) : (
<SubmitButton onClick={fetchNextTask}>Next Task</SubmitButton>
<SubmitButton data-cy="next-task" onClick={fetchNextTask}>
Next Task
</SubmitButton>
)}
</Flex>
</section>