E2E: Improve tests, lint, and add more drivers (#855)

This PR introduces lint to cypress code, adds drivers to try and
abstract the usage of cypress as much as possible.
Nothing very interesting, mainly to try out the driver pattern for the
e2e tests.
This commit is contained in:
Harel M
2023-12-27 20:58:24 +02:00
committed by GitHub
parent b784bf2b84
commit 124ae98bf3
12 changed files with 180 additions and 134 deletions

View File

@@ -37,7 +37,8 @@
"react/prop-types": ["off"],
// Disable no-undef. It's covered by @typescript-eslint
"no-undef": "off",
"indent": ["error", 2]
"indent": ["error", 2],
"no-var": ["error"]
},
"globals": {
"global": "readonly"

View File

@@ -1,7 +1,7 @@
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("accessibility", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, when, should } = new MaputnikDriver();
beforeAndAfter();
describe("skip links", () => {

View File

@@ -0,0 +1,41 @@
import { CypressHelper } from "@shellygo/cypress-test-utils";
export default class CypressWrapperDriver {
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
public given = {
...this.helper.given,
/**
*
* @param url a url to a file, this assumes the file name is the last part of the url
* @param alias
*/
interceptGetToFile(url: string) {
let fileNameAndAlias = url.split('/').pop();
cy.intercept('GET', url, { fixture: fileNameAndAlias }).as(fileNameAndAlias!);
},
interceptAndIgnore(url: string) {
cy.intercept({ method: "GET", url }, []);
}
}
public get = {
...this.helper.get,
elementByClassOrType(slector: string) {
return cy.get(slector);
}
}
public when = {
...this.helper.when,
visit(address: string) {
cy.visit(address);
},
confirmAlert() {
cy.on("window:confirm", () => true);
}
}
public beforeAndAfter = this.helper.beforeAndAfter;
}

View File

@@ -1,7 +1,7 @@
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("history", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, when, get, should } = new MaputnikDriver();
beforeAndAfter();
let undoKeyCombo: string;
@@ -15,11 +15,11 @@ describe("history", () => {
it("undo/redo", () => {
when.setStyle("geojson");
when.openLayersModal();
when.modal.open();
should.equalStyleStore((a: any) => a.layers, []);
when.fillLayersModal({
when.modal.fillLayers({
id: "step 1",
type: "background",
});
@@ -34,8 +34,8 @@ describe("history", () => {
]
);
when.openLayersModal();
when.fillLayersModal({
when.modal.open();
when.modal.fillLayers({
id: "step 2",
type: "background",
});

View File

@@ -1,7 +1,7 @@
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("keyboard", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, given, when, should } = new MaputnikDriver();
beforeAndAfter();
describe("shortcuts", () => {
beforeEach(() => {

View File

@@ -1,17 +1,17 @@
import { v1 as uuid } from "uuid";
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("layers", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, when, should } = new MaputnikDriver();
beforeAndAfter();
beforeEach(() => {
when.setStyle("both");
when.openLayersModal();
when.modal.open();
});
describe("ops", () => {
it("delete", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "background",
});
@@ -31,8 +31,7 @@ describe("layers", () => {
});
it("duplicate", () => {
var styleObj;
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "background",
});
@@ -64,8 +63,7 @@ describe("layers", () => {
});
it("hide", () => {
var styleObj;
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "background",
});
@@ -113,7 +111,7 @@ describe("layers", () => {
describe("background", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "background",
});
@@ -131,7 +129,7 @@ describe("layers", () => {
describe("modify", () => {
function createBackground() {
// Setup
var id = uuid();
let id = uuid();
when.selectWithin("add-layer.layer-type", "background");
when.setValue("add-layer.layer-id.input", "background:" + id);
@@ -154,11 +152,11 @@ describe("layers", () => {
describe("layer", () => {
it("expand/collapse");
it("id", () => {
var bgId = createBackground();
let bgId = createBackground();
when.click("layer-list-item:background:" + bgId);
var id = uuid();
let id = uuid();
when.setValue("layer-editor.layer-id.input", "foobar:" + id);
when.click("min-zoom");
@@ -174,7 +172,7 @@ describe("layers", () => {
});
it("min-zoom", () => {
var bgId = createBackground();
let bgId = createBackground();
when.click("layer-list-item:background:" + bgId);
when.setValue("min-zoom.input-text", "1");
@@ -203,7 +201,7 @@ describe("layers", () => {
});
it("max-zoom", () => {
var bgId = createBackground();
let bgId = createBackground();
when.click("layer-list-item:background:" + bgId);
when.setValue("max-zoom.input-text", "1");
@@ -223,8 +221,8 @@ describe("layers", () => {
});
it("comments", () => {
var bgId = createBackground();
var comment = "42";
let bgId = createBackground();
let comment = "42";
when.click("layer-list-item:background:" + bgId);
when.setValue("layer-comment.input", comment);
@@ -255,7 +253,7 @@ describe("layers", () => {
});
it("color", () => {
var bgId = createBackground();
let bgId = createBackground();
when.click("layer-list-item:background:" + bgId);
@@ -292,11 +290,11 @@ describe("layers", () => {
// TODO
it.skip("parse error", () => {
var bgId = createBackground();
let bgId = createBackground();
when.click("layer-list-item:background:" + bgId);
var errorSelector = ".CodeMirror-lint-marker-error";
let errorSelector = ".CodeMirror-lint-marker-error";
should.notExist(errorSelector);
when.click(".CodeMirror");
@@ -311,7 +309,7 @@ describe("layers", () => {
describe("fill", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "fill",
layer: "example",
});
@@ -334,7 +332,7 @@ describe("layers", () => {
describe("line", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "line",
layer: "example",
});
@@ -359,7 +357,7 @@ describe("layers", () => {
describe("symbol", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "symbol",
layer: "example",
});
@@ -379,7 +377,7 @@ describe("layers", () => {
describe("raster", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "raster",
layer: "raster",
});
@@ -399,7 +397,7 @@ describe("layers", () => {
describe("circle", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "circle",
layer: "example",
});
@@ -419,7 +417,7 @@ describe("layers", () => {
describe("fill extrusion", () => {
it("add", () => {
var id = when.fillLayersModal({
let id = when.modal.fillLayers({
type: "fill-extrusion",
layer: "example",
});
@@ -441,20 +439,20 @@ describe("layers", () => {
it("simple", () => {
when.setStyle("geojson");
when.openLayersModal();
when.fillLayersModal({
when.modal.open();
when.modal.fillLayers({
id: "foo",
type: "background",
});
when.openLayersModal();
when.fillLayersModal({
when.modal.open();
when.modal.fillLayers({
id: "foo_bar",
type: "background",
});
when.openLayersModal();
when.fillLayersModal({
when.modal.open();
when.modal.fillLayers({
id: "foo_bar_baz",
type: "background",
});

View File

@@ -1,18 +1,18 @@
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("map", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, when, should } = new MaputnikDriver();
beforeAndAfter();
describe("zoom level", () => {
it("via url", () => {
var zoomLevel = 12.37;
let zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel);
should.beVisible("maplibre:ctrl-zoom");
should.containText("maplibre:ctrl-zoom", "Zoom: " + zoomLevel);
});
it("via map controls", () => {
var zoomLevel = 12.37;
let zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel);
should.beVisible("maplibre:ctrl-zoom");

View File

@@ -1,7 +1,12 @@
import { CypressHelper } from "@shellygo/cypress-test-utils";
import { v1 as uuid } from "uuid";
import CypressWrapperDriver from "./cypress-wrapper-driver";
import ModalDriver from "./modal-driver";
const SERVER_ADDRESS = "http://localhost:8888/";
export default class MaputnikDriver {
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
private helper = new CypressWrapperDriver();
private modalDriver = new ModalDriver();
public beforeAndAfter = () => {
beforeEach(() => {
this.given.setupInterception();
@@ -11,36 +16,28 @@ export default class MaputnikDriver {
public given = {
setupInterception: () => {
cy.intercept("GET", "http://localhost:8888/example-style.json", {
fixture: "example-style.json",
}).as("example-style.json");
cy.intercept("GET", "http://localhost:8888/example-layer-style.json", {
fixture: "example-layer-style.json",
});
cy.intercept("GET", "http://localhost:8888/geojson-style.json", {
fixture: "geojson-style.json",
});
cy.intercept("GET", "http://localhost:8888/raster-style.json", {
fixture: "raster-style.json",
});
cy.intercept("GET", "http://localhost:8888/geojson-raster-style.json", {
fixture: "geojson-raster-style.json",
});
cy.intercept({ method: "GET", url: "*example.local/*" }, []);
cy.intercept({ method: "GET", url: "*example.com/*" }, []);
this.helper.given.interceptGetToFile(SERVER_ADDRESS + "example-style.json");
this.helper.given.interceptGetToFile(SERVER_ADDRESS + "example-layer-style.json");
this.helper.given.interceptGetToFile(SERVER_ADDRESS + "geojson-style.json");
this.helper.given.interceptGetToFile(SERVER_ADDRESS + "raster-style.json");
this.helper.given.interceptGetToFile(SERVER_ADDRESS + "geojson-raster-style.json");
this.helper.given.interceptAndIgnore("*example.local/*");
this.helper.given.interceptAndIgnore("*example.com/*");
},
};
public when = {
modal: this.modalDriver.when,
within: (selector: string, fn: () => void) => {
this.helper.when.within(fn, selector);
},
tab: () => cy.get("body").tab(),
tab: () => this.helper.get.elementByClassOrType("body").tab(),
waitForExampleFileRequset: () => {
this.helper.when.waitForResponse("example-style.json");
},
chooseExampleFile: () => {
cy.get("input[type='file']").selectFile(
this.helper.get.elementByClassOrType("input[type='file']").selectFile(
"cypress/fixtures/example-style.json",
{ force: true }
);
@@ -51,55 +48,34 @@ export default class MaputnikDriver {
) => {
let url = "?debug";
switch (styleProperties) {
case "geojson":
url += "&style=http://localhost:8888/geojson-style.json";
break;
case "raster":
url += "&style=http://localhost:8888/raster-style.json";
break;
case "both":
url += "&style=http://localhost:8888/geojson-raster-style.json";
break;
case "layer":
url += "&style=http://localhost:8888/example-layer-style.json";
break;
case "geojson":
url += `&style=${SERVER_ADDRESS}geojson-style.json`;
break;
case "raster":
url += `&style=${SERVER_ADDRESS}raster-style.json`;
break;
case "both":
url += `&style=${SERVER_ADDRESS}geojson-raster-style.json`;
break;
case "layer":
url += `&style=${SERVER_ADDRESS}/example-layer-style.json`;
break;
}
if (zoom) {
url += "#" + zoom + "/41.3805/2.1635";
url += `#${zoom}/41.3805/2.1635`;
}
cy.visit("http://localhost:8888/" + url);
this.helper.when.visit(SERVER_ADDRESS + url);
if (styleProperties) {
cy.on("window:confirm", () => true);
this.helper.when.confirmAlert();
}
this.helper.get.element("toolbar:link").should("be.visible");
},
fillLayersModal: (opts: {type: string, layer?: string, id?: string}) => {
var type = opts.type;
var layer = opts.layer;
var id;
if (opts.id) {
id = opts.id;
} else {
id = `${type}:${uuid()}`;
}
this.helper.get.element("add-layer.layer-type.select").select(type);
this.helper.get.element("add-layer.layer-id.input").type(id);
if (layer) {
this.when.within("add-layer.layer-source-block", () => {
cy.get("input").type(layer!);
})
}
this.when.click("add-layer");
return id;
},
typeKeys: (keys: string, selector?: string) => {
if (selector) {
this.helper.get.element(selector).type(keys);
} else {
cy.get("body").type(keys);
this.helper.get.elementByClassOrType("body").type(keys);
}
},
@@ -108,12 +84,12 @@ export default class MaputnikDriver {
},
clickZoomin: () => {
cy.get(".maplibregl-ctrl-zoom-in").click();
this.helper.get.elementByClassOrType(".maplibregl-ctrl-zoom-in").click();
},
selectWithin: (selector: string, value: string) => {
this.when.within(selector, () => {
cy.get("select").select(value);
this.helper.get.elementByClassOrType("select").select(value);
});
},
@@ -128,18 +104,6 @@ export default class MaputnikDriver {
setValue: (selector: string, text: string) => {
this.helper.get.element(selector).clear().type(text, { parseSpecialCharSequences: false });
},
closeModal: (key: string) => {
this.helper.when.waitUntil(() => this.helper.get.element(key));
this.when.click(key + ".close-modal");
},
openLayersModal: () => {
this.helper.when.click("layer-list:add-layer");
this.helper.get.element("modal:add-layer").should("exist");
this.helper.get.element("modal:add-layer").should("be.visible");
},
};
public get = {
@@ -153,18 +117,18 @@ export default class MaputnikDriver {
return obj;
},
exampleFileUrl: () => {
return "http://localhost:8888/example-style.json";
return SERVER_ADDRESS + "example-style.json";
},
};
public should = {
canvasBeFocused: () => {
this.when.within("maplibre:map", () => {
cy.get("canvas").should("be.focused");
this.helper.get.elementByClassOrType("canvas").should("be.focused");
});
},
notExist: (selector: string) => {
cy.get(selector).should("not.exist");
this.helper.get.elementByClassOrType(selector).should("not.exist");
},
beFocused: (selector: string) => {
this.helper.get.element(selector).should("have.focus");
@@ -192,7 +156,7 @@ export default class MaputnikDriver {
styleStoreEqualToExampleFileData: () => {
cy.window().then((win: any) => {
const obj = this.get.styleFromWindow(win);
cy.fixture("example-style.json").should("deep.equal", obj);
this.helper.given.fixture("example-style.json", "file:example-style.json").should("deep.equal", obj);
});
},

View File

@@ -0,0 +1,42 @@
import { v1 as uuid } from "uuid";
import CypressWrapperDriver from "./cypress-wrapper-driver";
export default class ModalDriver {
private helper = new CypressWrapperDriver();
public when = {
fillLayers: (opts: {type: string, layer?: string, id?: string}) => {
let type = opts.type;
let layer = opts.layer;
let id;
if (opts.id) {
id = opts.id;
} else {
id = `${type}:${uuid()}`;
}
this.helper.get.element("add-layer.layer-type.select").select(type);
this.helper.get.element("add-layer.layer-id.input").type(id);
if (layer) {
this.helper.when.within(() => {
this.helper.get.elementByClassOrType("input").type(layer!);
}, "add-layer.layer-source-block")
}
this.helper.when.click("add-layer");
return id;
},
open: () => {
this.helper.when.click("layer-list:add-layer");
this.helper.get.element("modal:add-layer").should("exist");
this.helper.get.element("modal:add-layer").should("be.visible");
},
close: (key: string) => {
this.helper.when.waitUntil(() => this.helper.get.element(key));
this.helper.when.click(key + ".close-modal");
},
}
}

View File

@@ -1,7 +1,7 @@
import MaputnikDriver from "./driver";
import MaputnikDriver from "./maputnik-driver";
describe("modals", () => {
let { beforeAndAfter, given, when, get, should } = new MaputnikDriver();
let { beforeAndAfter, when, get, should } = new MaputnikDriver();
beforeAndAfter();
beforeEach(() => {
when.setStyle("");
@@ -12,7 +12,7 @@ describe("modals", () => {
});
it("close", () => {
when.closeModal("modal:open");
when.modal.close("modal:open");
should.notExist("modal:open");
});
@@ -24,7 +24,7 @@ describe("modals", () => {
});
it("load from url", () => {
var styleFileUrl = get.exampleFileUrl();
let styleFileUrl = get.exampleFileUrl();
when.setValue("modal:open.url.input", styleFileUrl);
when.click("modal:open.url.button");
@@ -38,7 +38,7 @@ describe("modals", () => {
it("open/close", () => {
when.setStyle("");
when.typeKeys("?");
when.closeModal("modal:shortcuts");
when.modal.close("modal:shortcuts");
should.notExist("modal:shortcuts");
});
});
@@ -49,7 +49,7 @@ describe("modals", () => {
});
it("close", () => {
when.closeModal("modal:export");
when.modal.close("modal:export");
should.notExist("modal:export");
});
@@ -102,7 +102,7 @@ describe("modals", () => {
should.equalStyleStore((obj) => obj.sprite, "http://example.com");
});
it("glyphs url", () => {
var glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
let glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
when.setValue("modal:settings.glyphs", glyphsUrl);
when.click("modal:settings.name");
@@ -110,7 +110,7 @@ describe("modals", () => {
});
it("maptiler access token", () => {
var apiKey = "testing123";
let apiKey = "testing123";
when.setValue(
"modal:settings.maputnik:openmaptiles_access_token",
apiKey
@@ -124,7 +124,7 @@ describe("modals", () => {
});
it("thunderforest access token", () => {
var apiKey = "testing123";
let apiKey = "testing123";
when.setValue("modal:settings.maputnik:thunderforest_access_token", apiKey);
when.click("modal:settings.name");

View File

@@ -7,7 +7,7 @@
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"lint": "eslint ./src --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
"lint": "eslint ./src ./cypress --ext ts,tsx,js,jsx --report-unused-disable-directives --max-warnings 0",
"test": "cypress run",
"cy:open": "cypress open",
"lint-css": "stylelint \"src/styles/*.scss\"",

View File

@@ -21,7 +21,7 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"include": ["src", "cypress/e2e"],
"references": [{ "path": "./tsconfig.node.json" }],
// TODO: Remove when issue is resolved https://github.com/cypress-io/cypress/issues/27448
"ts-node": {