400 lines
16 KiB
JavaScript
400 lines
16 KiB
JavaScript
|
|
const fs = require("fs");
|
||
|
|
const puppeteer = require("puppeteer");
|
||
|
|
const { toMatchImageSnapshot } = require("jest-image-snapshot");
|
||
|
|
const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
|
||
|
|
expect.extend({ toMatchImageSnapshot });
|
||
|
|
|
||
|
|
require("dotenv").config();
|
||
|
|
const options = {
|
||
|
|
showPrefix: false,
|
||
|
|
showMatcherMessage: true,
|
||
|
|
showStack: true,
|
||
|
|
};
|
||
|
|
|
||
|
|
let browser;
|
||
|
|
let page;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
browser = await puppeteer.launch({
|
||
|
|
headless: true,
|
||
|
|
slowMo: 0,
|
||
|
|
devtools: false,
|
||
|
|
defaultViewport: {
|
||
|
|
width: 1024,
|
||
|
|
height: 768,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
page = await browser.newPage();
|
||
|
|
await page.setDefaultTimeout(10000);
|
||
|
|
await page.setDefaultNavigationTimeout(20000);
|
||
|
|
});
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
await page.goto(`http://localhost:${process.env.PORT}/products`);
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await browser.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Testing the products page title and content", () => {
|
||
|
|
it("should have the right title", async () => {
|
||
|
|
const title = await page.title();
|
||
|
|
expect(
|
||
|
|
title,
|
||
|
|
`The title for the web page "${title}" is wrong it should be "API-Experiment | Products" Make sure that the function handling the GET "/products" route is sending the right title`,
|
||
|
|
options
|
||
|
|
).toBe("API-Experiment | Products");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have a header with the text 'Products' with 'title' class", async () => {
|
||
|
|
const header = await page.$eval(".title", (el) => el.textContent);
|
||
|
|
expect(
|
||
|
|
header,
|
||
|
|
`The header with the text "Products" is not present on the page`,
|
||
|
|
options
|
||
|
|
).toBe("Products");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have the search form with the three inputs and the submit button", async () => {
|
||
|
|
const inputs = await page.$$("input");
|
||
|
|
expect(
|
||
|
|
inputs.length,
|
||
|
|
`The number of inputs in the search form is wrong, it should be 3`,
|
||
|
|
options
|
||
|
|
).toBe(3);
|
||
|
|
|
||
|
|
const button = await page.$eval(
|
||
|
|
"form > button.btn.btn-primary",
|
||
|
|
(el) => el.textContent
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
button,
|
||
|
|
`The submit button is not present on the page`,
|
||
|
|
options
|
||
|
|
).toBeTruthy();
|
||
|
|
expect(
|
||
|
|
button,
|
||
|
|
`The submit button should have the text "Search", but it has "${button}"`,
|
||
|
|
options
|
||
|
|
).toBe("Search");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have the correct form action and method", async () => {
|
||
|
|
const form = await page.$eval("form", (el) => ({
|
||
|
|
action: el.action,
|
||
|
|
method: el.method,
|
||
|
|
}));
|
||
|
|
expect(
|
||
|
|
form.action,
|
||
|
|
`The form action should be "http://localhost:${process.env.PORT}/products", but it has "${form.action}", You can change it in the "web/views/products/index.ejs" file.`,
|
||
|
|
options
|
||
|
|
).toBe(`http://localhost:${process.env.PORT}/products`);
|
||
|
|
expect(
|
||
|
|
form.method,
|
||
|
|
`The form method should be "GET", but it has "${form.method}", You can change it in the "web/views/products/index.ejs" file.`,
|
||
|
|
options
|
||
|
|
).toBe("get");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should the right inputs names and types", async () => {
|
||
|
|
const search_input = await page.$eval("#search", (el) => ({
|
||
|
|
name: el.name,
|
||
|
|
type: el.type,
|
||
|
|
}));
|
||
|
|
expect(
|
||
|
|
search_input.name,
|
||
|
|
`The first input should have the name "search", but it has "${search_input.name}"`,
|
||
|
|
options
|
||
|
|
).toBe("search");
|
||
|
|
expect(
|
||
|
|
search_input.type,
|
||
|
|
`The first input should have the type "text", but it has "${search_input.type}"`,
|
||
|
|
options
|
||
|
|
).toBe("text");
|
||
|
|
|
||
|
|
const minPrice_input = await page.$eval("#minPrice", (el) => ({
|
||
|
|
name: el.name,
|
||
|
|
type: el.type,
|
||
|
|
}));
|
||
|
|
expect(
|
||
|
|
minPrice_input.name,
|
||
|
|
`The second input should have the name "price[minPrice]", but it has "${minPrice_input.name}"`,
|
||
|
|
options
|
||
|
|
).toBe("price[minPrice]");
|
||
|
|
expect(
|
||
|
|
minPrice_input.type,
|
||
|
|
`The second input should have the type "number", but it has "${minPrice_input.type}"`,
|
||
|
|
options
|
||
|
|
).toBe("number");
|
||
|
|
|
||
|
|
const maxPrice_input = await page.$eval("#maxPrice", (el) => ({
|
||
|
|
name: el.name,
|
||
|
|
type: el.type,
|
||
|
|
}));
|
||
|
|
expect(
|
||
|
|
maxPrice_input.name,
|
||
|
|
`The third input should have the name "price[maxPrice]", but it has "${maxPrice_input.name}"`,
|
||
|
|
options
|
||
|
|
).toBe("price[maxPrice]");
|
||
|
|
expect(
|
||
|
|
maxPrice_input.type,
|
||
|
|
`The third input should have the type "number", but it has "${maxPrice_input.type}"`,
|
||
|
|
options
|
||
|
|
).toBe("number");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have a button to create a new product", async () => {
|
||
|
|
const button = await page.$eval(".btn.btn-primary", (el) => ({
|
||
|
|
text: el.textContent,
|
||
|
|
url: el.href,
|
||
|
|
}));
|
||
|
|
expect(
|
||
|
|
button.text,
|
||
|
|
`The button to create a new product should have the text "Create a new product", but it has "${button.text}"`,
|
||
|
|
options
|
||
|
|
).toBe("Create a new product");
|
||
|
|
expect(
|
||
|
|
button.url,
|
||
|
|
`The button to create a new product should have the url "http://localhost:${process.env.PORT}/products/create", but it has "${button.url}"`,
|
||
|
|
options
|
||
|
|
).toBe(`http://localhost:${process.env.PORT}/products/create`);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Testing the products page table", () => {
|
||
|
|
it("should have the right number of products", async () => {
|
||
|
|
const products = await page.$$("tbody tr");
|
||
|
|
expect(
|
||
|
|
products.length,
|
||
|
|
`The number of products is wrong, it should be 10`,
|
||
|
|
options
|
||
|
|
).toBe(10);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have the right data", async () => {
|
||
|
|
const products_data = await page.$$eval("tbody tr", (rows) =>
|
||
|
|
rows.map((row) => {
|
||
|
|
const [no, name, price, description, slug] = row.children;
|
||
|
|
return {
|
||
|
|
name: name.textContent,
|
||
|
|
price: parseFloat(price.textContent.replace("$", "")),
|
||
|
|
description: description.textContent,
|
||
|
|
slug: slug.children[0].href.split("/show/").pop(),
|
||
|
|
};
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
initial_data.forEach((product) => {
|
||
|
|
delete product._id;
|
||
|
|
delete product.createdAt;
|
||
|
|
delete product.updatedAt;
|
||
|
|
});
|
||
|
|
|
||
|
|
products_data.sort((a, b) => a.name.localeCompare(b.name));
|
||
|
|
initial_data.sort((a, b) => a.name.localeCompare(b.name));
|
||
|
|
|
||
|
|
expect(products_data, `The products data is wrong`, options).toEqual(
|
||
|
|
initial_data
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Testing the products details page", () => {
|
||
|
|
it("should go to the details page when clicking on a product", async () => {
|
||
|
|
await page.click("tbody tr:first-child a");
|
||
|
|
const url = await page.url();
|
||
|
|
const slug = url.split("/show/").pop();
|
||
|
|
const product = initial_data.find((product) => product.slug === slug);
|
||
|
|
expect(
|
||
|
|
product,
|
||
|
|
`The product with the slug "${slug}" is not present in the initial_data.json file`,
|
||
|
|
options
|
||
|
|
).toBeTruthy();
|
||
|
|
expect(
|
||
|
|
url,
|
||
|
|
`The url for the details page is wrong, it should be "http://localhost:${process.env.PORT}/products/show/${product.slug}", but it is "${url}"`,
|
||
|
|
options
|
||
|
|
).toBe(
|
||
|
|
`http://localhost:${process.env.PORT}/products/show/${product.slug}`
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have the button to edit and delete the product", async () => {
|
||
|
|
await page.click("tbody tr:first-child a");
|
||
|
|
const url = await page.url();
|
||
|
|
const slug = url.split("/show/").pop();
|
||
|
|
const product = initial_data.find((product) => product.slug === slug);
|
||
|
|
const productName = await page.$eval(
|
||
|
|
".card-title",
|
||
|
|
(el) => el.textContent
|
||
|
|
);
|
||
|
|
const productPrice = await page.$eval(
|
||
|
|
".card-subtitle",
|
||
|
|
(el) => el.textContent
|
||
|
|
);
|
||
|
|
const productDescription = await page.$eval(
|
||
|
|
".card-text",
|
||
|
|
(el) => el.textContent
|
||
|
|
);
|
||
|
|
const editButton = await page.$eval("a.btn", (el) => ({
|
||
|
|
text: el.textContent.trim(),
|
||
|
|
url: el.href.trim(),
|
||
|
|
}));
|
||
|
|
const deleteButton = await page.$eval("form > button.btn", (el) => ({
|
||
|
|
text: el.textContent.trim(),
|
||
|
|
url: el.parentElement.action.trim(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
expect(
|
||
|
|
productName,
|
||
|
|
`The product name is wrong, it should be "${product.name}", but it is "${productName}"`,
|
||
|
|
options
|
||
|
|
).toBe(product.name);
|
||
|
|
expect(
|
||
|
|
productPrice,
|
||
|
|
`The product price is wrong, it should be "${product.price}", but it is "$${productPrice}"`,
|
||
|
|
options
|
||
|
|
).toBe("$" + product.price);
|
||
|
|
expect(
|
||
|
|
productDescription,
|
||
|
|
`The product description is wrong, it should be "${product.description}", but it is "${productDescription}"`,
|
||
|
|
options
|
||
|
|
).toBe(product.description);
|
||
|
|
expect(
|
||
|
|
editButton.text,
|
||
|
|
`The edit button should have the text "Edit this product", but it has "${editButton.text}", change it in the "views/products/details.ejs" file`,
|
||
|
|
options
|
||
|
|
).toBe("Edit this product");
|
||
|
|
expect(
|
||
|
|
editButton.url,
|
||
|
|
`The edit button should have the url "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${editButton.url}", make sure you are using the correct slug by using "/products/update/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
|
||
|
|
options
|
||
|
|
).toBe(
|
||
|
|
`http://localhost:${process.env.PORT}/products/update/${product.slug}`
|
||
|
|
);
|
||
|
|
expect(
|
||
|
|
deleteButton.text,
|
||
|
|
`The delete button should have the text "Delete this product", but it has "${deleteButton.text}", change it in the "views/products/details.ejs" file`,
|
||
|
|
options
|
||
|
|
).toBe("Delete this product");
|
||
|
|
expect(
|
||
|
|
deleteButton.url,
|
||
|
|
`The delete button should have the url "http://localhost:${process.env.PORT}/products/delete/${product.slug}", but it has "${deleteButton.url}", make sure you are using the correct slug by using "/products/delete/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
|
||
|
|
options
|
||
|
|
).toBe(
|
||
|
|
`http://localhost:${process.env.PORT}/products/delete/${product.slug}`
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should don't go to the product's details page if the product does not exist", async () => {
|
||
|
|
await page.goto(
|
||
|
|
`http://localhost:${process.env.PORT}/products/show/123thisproductdoenotexist`
|
||
|
|
);
|
||
|
|
|
||
|
|
const title = await page.title();
|
||
|
|
expect(
|
||
|
|
title,
|
||
|
|
`The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET getProduct method return the error title if the product was not found`,
|
||
|
|
options
|
||
|
|
).toBe("API-Experiment | Error");
|
||
|
|
const statusCode = await page.$eval(".title", (el) => el.textContent);
|
||
|
|
expect(
|
||
|
|
statusCode,
|
||
|
|
`The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET getProduct method return the error status code if the product was not found`,
|
||
|
|
options
|
||
|
|
).toBe("404");
|
||
|
|
const message = await page.$eval(".message", (el) => el.textContent);
|
||
|
|
expect(
|
||
|
|
message,
|
||
|
|
`The message "${message}" is wrong it should be "No product found" Make sure that the function handling the GET getProduct method return the error message if the product was not found`,
|
||
|
|
options
|
||
|
|
).toBe("No product found");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Testing the product pages image snapshots", () => {
|
||
|
|
it("should have the right image for the products page", async () => {
|
||
|
|
if (!fs.existsSync("tests/web/images/products-table-page.png")) {
|
||
|
|
throw new Error(
|
||
|
|
`The reference image for the products table page does not exist, please import the image from the "tests/web/images/products-table-page.png"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const image = await page.screenshot({ fullPage: true });
|
||
|
|
expect(
|
||
|
|
image,
|
||
|
|
`The image for the products table page is wrong, it should be the same as the "tests/web/images/__diff_output__/products-table-page-diff.png" image`,
|
||
|
|
options
|
||
|
|
).toMatchImageSnapshot({
|
||
|
|
customDiffConfig: { threshold: 0.9 },
|
||
|
|
customSnapshotsDir: "tests/web/images",
|
||
|
|
customSnapshotIdentifier: "products-table-page",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should have the right image for the details page", async () => {
|
||
|
|
if (!fs.existsSync("tests/web/images/product-details-page.png")) {
|
||
|
|
throw new Error(
|
||
|
|
`The reference image for the product details page does not exist, please import the image from the "tests/web/images/product-details-page.png"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
await page.goto(
|
||
|
|
`http://localhost:${process.env.PORT}/products/show/${initial_data[0].slug}`
|
||
|
|
);
|
||
|
|
const image = await page.screenshot({ fullPage: true });
|
||
|
|
expect(
|
||
|
|
image,
|
||
|
|
`The image for the product details page is wrong, it should be the same as the "tests/web/images/__diff_output__/product-details-page-diff.png" image`,
|
||
|
|
options
|
||
|
|
).toMatchImageSnapshot({
|
||
|
|
customDiffConfig: { threshold: 0.9 },
|
||
|
|
customSnapshotsDir: "tests/web/images",
|
||
|
|
customSnapshotIdentifier: "product-details-page",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should match the not found product snapshot", async () => {
|
||
|
|
if (!fs.existsSync("tests/web/images/not-found-product-page.png")) {
|
||
|
|
throw new Error(
|
||
|
|
`The reference image for the not found product page does not exist, please import the image from the "tests/web/images/not-found-product-page.png"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
await page.goto(
|
||
|
|
`http://localhost:${process.env.PORT}/products/show/123`
|
||
|
|
);
|
||
|
|
const image = await page.screenshot({ fullPage: true });
|
||
|
|
expect(
|
||
|
|
image,
|
||
|
|
`The image for the not found product page is wrong, it should be the same as the "tests/web/images/__diff_output__/not-found-product-page-diff.png" image`
|
||
|
|
).toMatchImageSnapshot({
|
||
|
|
customDiffConfig: { threshold: 0.9 },
|
||
|
|
customSnapshotsDir: "tests/web/images",
|
||
|
|
customSnapshotIdentifier: "not-found-product-page",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should match no products found snapshot", async () => {
|
||
|
|
if (!fs.existsSync("tests/web/images/no-products-found-page.png")) {
|
||
|
|
throw new Error(
|
||
|
|
`The reference image for the no products found page does not exist, please import the image from the "tests/web/images/no-products-found-page.png"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
await page.goto(
|
||
|
|
`http://localhost:${process.env.PORT}/products?search=123ThisIsNotAProduct`
|
||
|
|
);
|
||
|
|
const image = await page.screenshot({ fullPage: true });
|
||
|
|
expect(
|
||
|
|
image,
|
||
|
|
`The image for the no products found page is wrong, it should be the same as the "tests/web/images/__diff_output__/no-products-found-page-diff.png" image`
|
||
|
|
).toMatchImageSnapshot({
|
||
|
|
customDiffConfig: { threshold: 0.9 },
|
||
|
|
customSnapshotsDir: "tests/web/images",
|
||
|
|
customSnapshotIdentifier: "no-products-found-page",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|