Bug 32030: ERM - Add "integration" tests using Cypress
We are mocking the REST API routes responses here, we could do better, but it's a nice first step. To run the tests: From the host (ie. *not* inside ktd): `yarn run cypress open` Signed-off-by: Jonathan Field <jonathan.field@ptfs-europe.com> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
parent
48c1c03127
commit
b0c6ca0e2b
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"baseUrl": "http://kohadev-intra.mydnsname.org:8081"
|
||||
}
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
import { mount } from "@cypress/vue";
|
||||
const dayjs = require("dayjs"); /* Cannot use our calendar JS code, it's in an include file (!)
|
||||
Also note that moment.js is deprecated */
|
||||
|
||||
const dates = {
|
||||
today_iso: dayjs().format("YYYY-MM-DD"),
|
||||
today_us: dayjs().format("MM/DD/YYYY"),
|
||||
tomorrow_iso: dayjs().add(1, "day").format("YYYY-MM-DD"),
|
||||
tomorrow_us: dayjs().add(1, "day").format("MM/DD/YYYY"),
|
||||
};
|
||||
function get_agreement() {
|
||||
return {
|
||||
agreement_id: 1,
|
||||
closure_reason: "",
|
||||
description: "my first agreement",
|
||||
is_perpetual: false,
|
||||
license_info: "",
|
||||
name: "agreement 1",
|
||||
renewal_priority: "",
|
||||
status: "active",
|
||||
vendor_id: null,
|
||||
periods: [
|
||||
{
|
||||
started_on: dates["today_iso"],
|
||||
ended_on: dates["tomorrow_iso"],
|
||||
cancellation_deadline: null,
|
||||
notes: null,
|
||||
},
|
||||
{
|
||||
started_on: dates["today_iso"],
|
||||
ended_on: null,
|
||||
cancellation_deadline: dates["tomorrow_iso"],
|
||||
notes: "this is a note",
|
||||
},
|
||||
],
|
||||
user_roles: [],
|
||||
};
|
||||
}
|
||||
|
||||
describe("Agreement CRUD operations", () => {
|
||||
beforeEach(() => {
|
||||
cy.login("koha", "koha");
|
||||
cy.title().should("eq", "Koha staff interface");
|
||||
});
|
||||
|
||||
it("List agreements", () => {
|
||||
// GET agreements returns 500
|
||||
cy.intercept("GET", "/api/v1/erm/agreements*", {
|
||||
statusCode: 500,
|
||||
error: "Something went wrong",
|
||||
});
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
cy.get("#agreements").contains("Something went wrong");
|
||||
|
||||
// GET agreements returns empty list
|
||||
cy.intercept("GET", "/api/v1/erm/agreements*", []);
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
cy.get("#agreements").contains("There are no agreements defined.");
|
||||
|
||||
// GET agreements returns something
|
||||
let agreement = get_agreement();
|
||||
let agreements = [agreement];
|
||||
|
||||
cy.intercept("GET", "/api/v1/erm/agreements*", {
|
||||
statusCode: 200,
|
||||
body: agreements,
|
||||
headers: {
|
||||
"X-Base-Total-Count": "1",
|
||||
"X-Total-Count": "1",
|
||||
},
|
||||
});
|
||||
cy.intercept("GET", "/api/v1/erm/agreements/*", agreement);
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
cy.get("#agreements").contains("Showing 1 to 1 of 1 entries");
|
||||
});
|
||||
|
||||
it ("Add agreement", () => {
|
||||
// Click the button in the toolbar
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
cy.contains("New agreement").click();
|
||||
cy.get("#agreements h2").contains("New agreement");
|
||||
|
||||
// Fill in the form for normal attributes
|
||||
let agreement = get_agreement();
|
||||
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("input:invalid,textarea:invalid,select:invalid").should("have.length", 3);
|
||||
cy.get("#agreement_name").type(agreement.name);
|
||||
cy.get("#agreement_description").type(agreement.description);
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("input:invalid,textarea:invalid,select:invalid").should("have.length", 1); // name, description, status
|
||||
cy.get("#agreement_status").select(agreement.status);
|
||||
|
||||
cy.contains("Add new period").click();
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("input:invalid,textarea:invalid,select:invalid").should("have.length", 1); // Start date
|
||||
|
||||
// Add new periods
|
||||
cy.contains("Add new period").click();
|
||||
cy.contains("Add new period").click();
|
||||
cy.get("#agreement_periods > fieldset").should("have.length", 3);
|
||||
|
||||
cy.get("#agreement_period_1").contains("Remove this period").click();
|
||||
|
||||
cy.get("#agreement_periods > fieldset").should("have.length", 2);
|
||||
cy.get("#agreement_period_0");
|
||||
cy.get("#agreement_period_1");
|
||||
|
||||
// Selecting the flatpickr values is a bit tedious here...
|
||||
// We have 3 date inputs per period
|
||||
cy.get("#ended_on_0").click();
|
||||
// Second flatpickr => ended_on for the first period
|
||||
cy.get(".flatpickr-calendar")
|
||||
.eq(1)
|
||||
.find("span.today")
|
||||
.click({ force: true }); // select today. No idea why we should force, but there is a random failure otherwise
|
||||
|
||||
cy.get("#started_on_0").click();
|
||||
cy.get(".flatpickr-calendar")
|
||||
.eq(0)
|
||||
.find("span.today")
|
||||
.next("span")
|
||||
.click(); // select tomorrow
|
||||
|
||||
cy.get("#ended_on_0").should("have.value", ""); // Has been reset correctly
|
||||
|
||||
cy.get("#started_on_0").click();
|
||||
cy.get(".flatpickr-calendar").eq(0).find("span.today").click(); // select today
|
||||
cy.get("#ended_on_0").click({ force: true }); // No idea why we should force, but there is a random failure otherwise
|
||||
cy.get(".flatpickr-calendar")
|
||||
.eq(1)
|
||||
.find("span.today")
|
||||
.next("span")
|
||||
.click(); // select tomorrow
|
||||
|
||||
// Second period
|
||||
cy.get("#started_on_1").click({ force: true });
|
||||
cy.get(".flatpickr-calendar").eq(3).find("span.today").click(); // select today
|
||||
cy.get("#cancellation_deadline_1").click();
|
||||
cy.get(".flatpickr-calendar")
|
||||
.eq(5)
|
||||
.find("span.today")
|
||||
.next("span")
|
||||
.click(); // select tomorrow
|
||||
cy.get("#notes_1").type("this is a note");
|
||||
|
||||
// TODO Add a new user
|
||||
// How to test a new window with cypresS?
|
||||
//cy.contains("Add new user").click();
|
||||
//cy.contains("Select user").click();
|
||||
|
||||
// Submit the form, get 500
|
||||
cy.intercept("POST", "/api/v1/erm/agreements", {
|
||||
statusCode: 500,
|
||||
error: "Something went wrong",
|
||||
});
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("#agreements").contains(
|
||||
"Something went wrong: Internal Server Error"
|
||||
);
|
||||
|
||||
// Submit the form, success!
|
||||
cy.intercept("POST", "/api/v1/erm/agreements", {
|
||||
statusCode: 201,
|
||||
body: agreement,
|
||||
});
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("#agreements").contains("Agreement created");
|
||||
});
|
||||
|
||||
it ("Edit agreement", () => {
|
||||
let agreement = get_agreement();
|
||||
let agreements = [agreement];
|
||||
// Click the 'Edit' button from the list
|
||||
cy.intercept("GET", "/api/v1/erm/agreements*", {
|
||||
statusCode: 200,
|
||||
body: agreements,
|
||||
headers: {
|
||||
"X-Base-Total-Count": "1",
|
||||
"X-Total-Count": "1",
|
||||
},
|
||||
});
|
||||
cy.intercept("GET", "/api/v1/erm/agreements/*", agreement).as("get-agreement");
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
cy.get("#agreements table tbody tr:first").contains("Edit").click();
|
||||
cy.wait("@get-agreement");
|
||||
cy.wait(500); // Cypress is too fast! Vue hasn't populated the form yet!
|
||||
cy.get("#agreements h2").contains("Edit agreement");
|
||||
|
||||
// Form has been correctly filled in
|
||||
cy.get("#agreement_name").should("have.value", agreements[0].name);
|
||||
cy.get("#agreement_description").should(
|
||||
"have.value",
|
||||
agreements[0].description
|
||||
);
|
||||
cy.get("#agreement_status").should("have.value", agreement.status);
|
||||
cy.get("#agreement_is_perpetual_no").should("be.checked");
|
||||
cy.get("#started_on_0").invoke("val").should("eq", dates["today_us"]);
|
||||
cy.get("#ended_on_0").invoke("val").should("eq", dates["tomorrow_us"]);
|
||||
cy.get("#cancellation_deadline_0").invoke("val").should("eq", "");
|
||||
cy.get("#notes_0").should("have.value", "");
|
||||
cy.get("#started_on_1").invoke("val").should("eq", dates["today_us"]);
|
||||
cy.get("#ended_on_1").invoke("val").should("eq", "");
|
||||
cy.get("#cancellation_deadline_1")
|
||||
.invoke("val")
|
||||
.should("eq", dates["tomorrow_us"]);
|
||||
cy.get("#notes_1").should("have.value", "this is a note");
|
||||
|
||||
// Submit the form, get 500
|
||||
cy.intercept("PUT", "/api/v1/erm/agreements/*", {
|
||||
statusCode: 500,
|
||||
error: "Something went wrong",
|
||||
});
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("#agreements").contains(
|
||||
"Something went wrong: Internal Server Error"
|
||||
);
|
||||
|
||||
// Submit the form, success!
|
||||
cy.intercept("PUT", "/api/v1/erm/agreements/*", {
|
||||
statusCode: 200,
|
||||
body: agreement,
|
||||
});
|
||||
cy.get("#agreements").contains("Submit").click();
|
||||
cy.get("#agreements").contains("Agreement updated");
|
||||
});
|
||||
|
||||
it ("Delete agreement", () => {
|
||||
let agreement = get_agreement();
|
||||
let agreements = [agreement];
|
||||
|
||||
// Click the 'Delete' button from the list
|
||||
cy.intercept("GET", "/api/v1/erm/agreements*", {
|
||||
statusCode: 200,
|
||||
body: agreements,
|
||||
headers: {
|
||||
"X-Base-Total-Count": "1",
|
||||
"X-Total-Count": "1",
|
||||
},
|
||||
});
|
||||
cy.intercept("GET", "/api/v1/erm/agreements/*", agreement);
|
||||
cy.visit("/cgi-bin/koha/erm/agreements.pl");
|
||||
|
||||
cy.get("#agreements table tbody tr:first").contains("Delete").click();
|
||||
cy.get("#agreements h2").contains("Delete agreement");
|
||||
cy.contains("Agreement name: " + agreement.name);
|
||||
|
||||
// Submit the form, get 500
|
||||
cy.intercept("DELETE", "/api/v1/erm/agreements/*", {
|
||||
statusCode: 500,
|
||||
error: "Something went wrong",
|
||||
});
|
||||
cy.contains("Yes, delete").click();
|
||||
cy.get("#agreements").contains(
|
||||
"Something went wrong: Internal Server Error"
|
||||
);
|
||||
|
||||
// Submit the form, success!
|
||||
cy.intercept("DELETE", "/api/v1/erm/agreements/*", {
|
||||
statusCode: 204,
|
||||
body: null,
|
||||
});
|
||||
cy.contains("Yes, delete").click();
|
||||
cy.get("#agreements").contains("Agreement deleted");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
const { startDevServer } = require('@cypress/webpack-dev-server')
|
||||
const webpackConfig = require('@vue/cli-service/webpack.config.js')
|
||||
|
||||
module.exports = (on, config) => {
|
||||
on('dev-server:start', options =>
|
||||
startDevServer({
|
||||
options,
|
||||
webpackConfig
|
||||
})
|
||||
)
|
||||
|
||||
return config
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
|
||||
Cypress.Commands.add('login', (username, password) => {
|
||||
cy.visit('/cgi-bin/koha/mainpage.pl?logout.x=1')
|
||||
cy.get("#userid").type(username)
|
||||
cy.get("#password").type(password)
|
||||
cy.get("#submit-button").click()
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<fieldset class="rows" id="agreement_periods">
|
||||
<legend>Periods</legend>
|
||||
<fieldset
|
||||
:id="`agreement_period_${counter}`"
|
||||
class="rows"
|
||||
v-for="(period, counter) in periods"
|
||||
v-bind:key="counter"
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
>Start date:
|
||||
</label>
|
||||
<flat-pickr
|
||||
:id="`started_on_${counter}`"
|
||||
v-model="period.started_on"
|
||||
required
|
||||
:config="fp_config"
|
||||
|
|
@ -46,6 +48,7 @@
|
|||
<li>
|
||||
<label :for="`notes_${counter}`">Notes: </label>
|
||||
<input
|
||||
:id="`notes_${counter}`"
|
||||
type="text"
|
||||
class="notes"
|
||||
:name="`notes_${counter}`"
|
||||
|
|
@ -74,6 +77,8 @@ export default {
|
|||
if (!this.dates_fixed) {
|
||||
this.periods.forEach(p => {
|
||||
p.started_on = $date(p.started_on)
|
||||
p.ended_on = $date(p.ended_on)
|
||||
p.cancellation_deadline = $date(p.cancellation_deadline)
|
||||
})
|
||||
this.dates_fixed = 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,18 @@
|
|||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cypress/vue": "^3.1.1",
|
||||
"@cypress/webpack-dev-server": "^1.8.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.0.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.0-5",
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"@vue/cli-service": "^5.0.1",
|
||||
"babel-core": "^7.0.0-beta.3",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-vue-3": "^0.1.7",
|
||||
"css-loader": "^6.6.0",
|
||||
"cypress": "^9.5.2",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^4.0.0",
|
||||
"gulp-concat-po": "^1.0.0",
|
||||
|
|
@ -28,6 +32,7 @@
|
|||
"lodash": "^4.17.12",
|
||||
"merge-stream": "^2.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"mysql": "^2.18.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"vue": "^3.2.31",
|
||||
"vue-flatpickr-component": "^9"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
}
|
||||
},
|
||||
"exclude": ["./cypress"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,19 @@ module.exports = {
|
|||
{
|
||||
test: /\.vue$/,
|
||||
loader: "vue-loader",
|
||||
exclude: [path.resolve(__dirname, "cypress/")],
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
appendTsSuffixTo: [/\.vue$/]
|
||||
}
|
||||
},
|
||||
exclude: [path.resolve(__dirname, "cypress/")],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
use: ['style-loader', 'css-loader'],
|
||||
}
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue