test: add full application BDD suite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
bed3f9a019
commit
79e09f049f
14 changed files with 1408 additions and 4 deletions
2
bdd/.dockerignore
Normal file
2
bdd/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
reports
|
||||
8
bdd/Dockerfile
Normal file
8
bdd/Dockerfile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
FROM mcr.microsoft.com/playwright:v1.63.0-noble
|
||||
|
||||
WORKDIR /work
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
|
||||
CMD ["npm", "test"]
|
||||
9
bdd/cucumber.mjs
Normal file
9
bdd/cucumber.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export default {
|
||||
paths: ["features/**/*.feature"],
|
||||
import: ["support/**/*.js", "steps/**/*.js"],
|
||||
format: ["progress", "html:reports/cucumber.html"],
|
||||
formatOptions: {
|
||||
snippetInterface: "async-await",
|
||||
},
|
||||
parallel: 1,
|
||||
};
|
||||
86
bdd/features/recruitment-workspace.feature
Normal file
86
bdd/features/recruitment-workspace.feature
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
Feature: Recruitment workspace
|
||||
As an interview team
|
||||
We want one connected workspace for jobs, candidates, assessments, and interviews
|
||||
So that we can move qualified people through a consistent hiring process
|
||||
|
||||
Scenario: User story - a valid interviewer signs in
|
||||
Given the Go Guess application is open
|
||||
When I sign in with email "integration@example.com" and password "integration-password"
|
||||
Then I should see the heading "Your hiring pipeline, at a glance."
|
||||
And I should be on the "/" page
|
||||
|
||||
Scenario: User story - invalid credentials are rejected
|
||||
Given the Go Guess application is open
|
||||
When I sign in with email "integration@example.com" and password "incorrect-password"
|
||||
Then I should see the error "invalid email or password"
|
||||
And I should see the heading "Sign in to your workspace"
|
||||
|
||||
Scenario: User story - an interviewer gets a quick pipeline overview
|
||||
Given I am signed in as a valid interviewer
|
||||
Then I should see the heading "Your hiring pipeline, at a glance."
|
||||
And the dashboard should link to jobs, participants, and questions
|
||||
And I should see "Integration Backend Engineer"
|
||||
|
||||
Scenario: User story - an interviewer creates a job posting
|
||||
Given I am signed in as a valid interviewer
|
||||
When I follow the "Jobs" link
|
||||
And I follow the "Create job" link
|
||||
And I fill in the form:
|
||||
| Job title | BDD Platform Engineer |
|
||||
| Position | Platform Engineer |
|
||||
| Seniority | Senior |
|
||||
| Labels | Backend |
|
||||
| Description | Build reliable production infrastructure. |
|
||||
| Required skills | Go, PostgreSQL |
|
||||
| Additional skills | Docker |
|
||||
And I click the "Create job" button
|
||||
Then I should see the heading "BDD Platform Engineer"
|
||||
And I should see "Build reliable production infrastructure."
|
||||
And I should see "draft"
|
||||
|
||||
Scenario: User story - an interviewer adds a participant
|
||||
Given I am signed in as a valid interviewer
|
||||
When I follow the "Participants" link
|
||||
And I follow the "Add participant" link
|
||||
And I fill in the form:
|
||||
| First name | Beatrice |
|
||||
| Last name | Driven |
|
||||
| Birthday | 1993-06-14 |
|
||||
| Email address | beatrice.bdd@example.com |
|
||||
| Contact information | Brussels, +32 470 11 22 33 |
|
||||
And I click the "Create participant" button
|
||||
Then I should see the heading "Beatrice Driven"
|
||||
And I should see "beatrice.bdd@example.com"
|
||||
|
||||
Scenario: User story - an interviewer creates a reusable assessment question
|
||||
Given I am signed in as a valid interviewer
|
||||
When I follow the "Question library" link
|
||||
And I fill in the form:
|
||||
| Question prompt | How do you keep a deployment reversible? |
|
||||
| Reference answer | Use versioned artifacts and tested rollback procedures. |
|
||||
And I click the "Create question" button
|
||||
Then I should see "How do you keep a deployment reversible?"
|
||||
And I should see "Use versioned artifacts and tested rollback procedures."
|
||||
|
||||
Scenario: User story - an interviewer invites a matching candidate
|
||||
Given I am signed in as a valid interviewer
|
||||
When I follow the "Jobs" link
|
||||
And I follow the "Integration Backend Engineer" link
|
||||
And I select the "Candidate match" tab
|
||||
Then I should see "Integration Candidate"
|
||||
And I should see "100%"
|
||||
When I select the "Invitations" tab
|
||||
And I click the "Generate invitation" button
|
||||
Then an invitation should be shown for "Integration Candidate"
|
||||
|
||||
Scenario: User story - an interviewer can reach collaboration areas
|
||||
Given I am signed in as a valid interviewer
|
||||
When I follow the "Inbox" link
|
||||
Then I should see the heading "Inbox"
|
||||
And I should see "Inbox zero"
|
||||
When I follow the "Calendar" link
|
||||
Then I should be on the "/calendar" page
|
||||
And I should see "Schedule"
|
||||
When I follow the "Users" link
|
||||
Then I should see the heading "Interview team"
|
||||
And I should see "Integration Interviewer"
|
||||
1082
bdd/package-lock.json
generated
Normal file
1082
bdd/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
13
bdd/package.json
Normal file
13
bdd/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "go-guess-bdd",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "cucumber-js --config cucumber.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cucumber/cucumber": "13.2.1",
|
||||
"playwright": "1.63.0"
|
||||
}
|
||||
}
|
||||
93
bdd/steps/workspace.steps.js
Normal file
93
bdd/steps/workspace.steps.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { Given, Then, When } from "@cucumber/cucumber";
|
||||
|
||||
async function openLoginPage(world) {
|
||||
await world.page.goto(world.baseURL);
|
||||
await world.page
|
||||
.getByRole("heading", { name: "Sign in to your workspace", exact: true })
|
||||
.waitFor();
|
||||
}
|
||||
|
||||
async function signIn(world, email, password) {
|
||||
await world.page.getByLabel("Email address", { exact: true }).fill(email);
|
||||
await world.page.getByLabel("Password", { exact: true }).fill(password);
|
||||
await world.page
|
||||
.getByRole("button", { name: "Sign in", exact: true })
|
||||
.click();
|
||||
}
|
||||
|
||||
Given("the Go Guess application is open", async function () {
|
||||
await openLoginPage(this);
|
||||
});
|
||||
|
||||
Given("I am signed in as a valid interviewer", async function () {
|
||||
await openLoginPage(this);
|
||||
await signIn(this, "integration@example.com", "integration-password");
|
||||
await this.page
|
||||
.getByRole("heading", {
|
||||
name: "Your hiring pipeline, at a glance.",
|
||||
exact: true,
|
||||
})
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
When(
|
||||
"I sign in with email {string} and password {string}",
|
||||
async function (email, password) {
|
||||
await signIn(this, email, password);
|
||||
},
|
||||
);
|
||||
|
||||
When("I follow the {string} link", async function (name) {
|
||||
await this.page.getByRole("link", { name, exact: true }).first().click();
|
||||
});
|
||||
|
||||
When("I click the {string} button", async function (name) {
|
||||
await this.page.getByRole("button", { name, exact: true }).click();
|
||||
});
|
||||
|
||||
When("I select the {string} tab", async function (name) {
|
||||
await this.page.getByRole("tab", { name: new RegExp(`^${name}`) }).click();
|
||||
});
|
||||
|
||||
When("I fill in the form:", async function (table) {
|
||||
for (const [label, value] of table.raw()) {
|
||||
await this.page.getByLabel(label, { exact: true }).fill(value);
|
||||
}
|
||||
});
|
||||
|
||||
Then("I should see the heading {string}", async function (heading) {
|
||||
await this.page
|
||||
.getByRole("heading", { name: heading, exact: true })
|
||||
.waitFor();
|
||||
});
|
||||
|
||||
Then("I should see {string}", async function (text) {
|
||||
await this.page.getByText(text, { exact: true }).first().waitFor();
|
||||
});
|
||||
|
||||
Then("I should see the error {string}", async function (message) {
|
||||
await this.page.getByRole("alert").filter({ hasText: message }).waitFor();
|
||||
});
|
||||
|
||||
Then("I should be on the {string} page", async function (path) {
|
||||
await this.page.waitForURL((url) => url.pathname === path);
|
||||
assert.equal(new URL(this.page.url()).pathname, path);
|
||||
});
|
||||
|
||||
Then(
|
||||
"the dashboard should link to jobs, participants, and questions",
|
||||
async function () {
|
||||
for (const path of ["/jobs", "/participants", "/questions"]) {
|
||||
await this.page.locator(`a[href="${path}"]`).first().waitFor();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Then("an invitation should be shown for {string}", async function (candidate) {
|
||||
await this.page.getByText(candidate, { exact: true }).last().waitFor();
|
||||
const invitationURL = await this.page
|
||||
.locator(".invitation-row code")
|
||||
.textContent();
|
||||
assert.match(invitationURL ?? "", /\/participant\/[a-zA-Z0-9_-]+/);
|
||||
});
|
||||
24
bdd/support/hooks.js
Normal file
24
bdd/support/hooks.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { After, Before, Status, setDefaultTimeout } from "@cucumber/cucumber";
|
||||
import { chromium } from "playwright";
|
||||
|
||||
setDefaultTimeout(Number(process.env.BDD_STEP_TIMEOUT_MS ?? 15000));
|
||||
|
||||
Before(async function () {
|
||||
this.browser = await chromium.launch({
|
||||
headless: process.env.BDD_HEADLESS !== "false",
|
||||
});
|
||||
this.context = await this.browser.newContext();
|
||||
this.page = await this.context.newPage();
|
||||
this.baseURL = process.env.BDD_BASE_URL ?? "http://127.0.0.1:8080";
|
||||
});
|
||||
|
||||
After(async function ({ result }) {
|
||||
if (result?.status === Status.FAILED && this.page) {
|
||||
await this.attach(
|
||||
await this.page.screenshot({ fullPage: true }),
|
||||
"image/png",
|
||||
);
|
||||
}
|
||||
await this.context?.close();
|
||||
await this.browser?.close();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue