Skip to content
Snippets Groups Projects
Unverified Commit 06f14c01 authored by Damon P. Cortesi's avatar Damon P. Cortesi Committed by GitHub
Browse files

Feature/cypress mongo (#12292)

* Add database-specific tests to Cypress (starting with Mongo)
* Initial changes to Cypress to allow a limited set of tests to run
* Include drivers in frontend uberjar
parent a1bcd74a
No related branches found
No related tags found
No related merge requests found
......@@ -170,6 +170,11 @@ executors:
ACCEPT_EULA: Y
SA_PASSWORD: 'P@ssw0rd'
fe-mongo:
working_directory: /home/circleci/metabase/metabase/
docker:
- image: circleci/clojure:lein-2.8.1-node-browsers
- image: circleci/mongo:4.0
########################################################################################################################
......@@ -279,6 +284,13 @@ jobs:
done;
echo `md5sum yarn.lock` >> frontend-checksums.txt
echo `md5sum webpack.config.js` >> frontend-checksums.txt
# As well as driver modules (database drivers)
- run:
name: Generate checksums of all driver module source files to use as Uberjar cache key
command: >
for file in `find ./modules -type f | sort`;
do echo `md5sum $file` >> modules-checksums.txt;
done;
- run:
name: Save last git commit message
command: git log -1 --oneline > commit.txt
......@@ -530,18 +542,18 @@ jobs:
- restore-be-deps-cache
- restore_cache:
keys:
- uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}
- uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
- run:
name: Build uberjar if needed
command: >
if [ ! -f './target/uberjar/metabase.jar' ];
then ./bin/build version frontend uberjar;
then ./bin/build version frontend drivers uberjar;
fi
no_output_timeout: 5m
- store_artifacts:
path: /home/circleci/metabase/metabase/target/uberjar/metabase.jar
- save_cache:
key: uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}
key: uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
paths:
- /home/circleci/metabase/metabase/target/uberjar/metabase.jar
......@@ -560,11 +572,24 @@ jobs:
command: ./bin/build version
fe-tests-cypress:
executor: clojure-and-node
parameters:
e:
type: executor
default: clojure-and-node
only-single-database:
type: boolean
default: false
test-files-location:
type: string
default: ""
driver:
type: string
default: ""
executor: << parameters.e >>
steps:
- run-yarn-command:
command-name: Run Cypress tests
command: run test-cypress-no-build
command: run test-cypress-no-build <<# parameters.only-single-database >> --testFiles << parameters.test-files-location >> <</ parameters.only-single-database >>
before-steps:
- restore_cache:
keys:
......@@ -852,6 +877,16 @@ workflows:
- build-uberjar
- fe-deps
- fe-tests-cypress:
name: fe-tests-cypress-mongo
requires:
- build-uberjar
- fe-deps
e: fe-mongo
driver: mongo
only-single-database: true
test-files-location: frontend/test/metabase-db/mongo
- deploy-master:
requires:
- yaml-linter
......
......@@ -7,8 +7,13 @@ const BackendResource = require("./backend.js").BackendResource;
const server = BackendResource.get({ dbKey: "" });
// We currently accept two (optional) command line arguments
// --open - Opens the Cypress test browser
// --testFiles <path> - Specifies a different path for the integration folder
const userArgs = process.argv.slice(2);
const isOpenMode = userArgs[0] === "--open";
const isOpenMode = userArgs.includes("--open");
const testFiles = userArgs.includes("--testFiles");
const testFilesLocation = userArgs[userArgs.indexOf("--testFiles") + 1];
function readFile(fileName) {
return new Promise(function(resolve, reject) {
......@@ -30,6 +35,10 @@ const init = async () => {
);
}
if (testFiles) {
console.log(chalk.bold(`Running tests in '${testFilesLocation}'`));
}
try {
const version = await readFile(
__dirname + "/../../../resources/version.properties",
......@@ -54,6 +63,11 @@ const init = async () => {
await BackendResource.start(server);
console.log(chalk.bold("Starting Cypress"));
let commandLineConfig = `baseUrl=${server.host}`;
if (testFiles) {
commandLineConfig = `${commandLineConfig},integrationFolder=${testFilesLocation}`;
}
const cypressProcess = spawn(
"yarn",
[
......@@ -62,7 +76,7 @@ const init = async () => {
"--config-file",
process.env["CONFIG_FILE"],
"--config",
`baseUrl=${server.host}`,
commandLineConfig,
...(process.env["CI"]
? [
"--reporter",
......
......@@ -68,6 +68,8 @@ export function main() {
return cy.get("nav").next();
}
// Metabase utility functions for commonly-used patterns
export function openOrdersTable() {
cy.visit("/question/new?database=1&table=2");
}
......@@ -76,4 +78,13 @@ export function openProductsTable() {
cy.visit("/question/new?database=1&table=1");
}
// Find a text field by label text, type it in, then blur the field.
// Commonly used in our Admin section as we auto-save settings.
export function typeAndBlurUsingLabel(label, value) {
cy.findByLabelText(label)
.clear()
.type(value)
.blur();
}
Cypress.on("uncaught:exception", (err, runnable) => false);
{
"testFiles": "**/*.cy.spec.js",
"pluginsFile": "frontend/test/cypress-plugins.js",
"integrationFolder": "frontend/test",
"integrationFolder": "frontend/test/metabase",
"supportFile": "frontend/test/__support__/cypress.js",
"viewportHeight": 800,
"viewportWidth": 1280
......
import {
signInAsAdmin,
restore,
modal,
typeAndBlurUsingLabel,
} from "__support__/cypress";
function addMongoDatabase() {
cy.visit("/admin/databases/create");
cy.contains("Database type")
.closest(".Form-field")
.find("a")
.click();
cy.contains("MongoDB").click({ force: true });
cy.contains("Additional Mongo connection");
typeAndBlurUsingLabel("Name", "MongoDB");
typeAndBlurUsingLabel("Database name", "admin");
cy.findByText("Save")
.should("not.be.disabled")
.click();
}
describe("mongodb > admin > add", () => {
beforeEach(() => {
restore();
signInAsAdmin();
cy.server();
});
it("should add a database and redirect to listing", () => {
cy.route({
method: "POST",
url: "/api/database",
}).as("createDatabase");
addMongoDatabase();
cy.wait("@createDatabase");
cy.url().should("match", /\/admin\/databases\?created=\d+$/);
cy.contains("Your database has been added!");
modal()
.contains("I'm good thanks")
.click();
});
it("can query a Mongo database", () => {
addMongoDatabase();
cy.url().should("match", /\/admin\/databases\?created=\d+$/);
cy.visit("/question/new");
cy.contains("Simple question").click();
cy.contains("MongoDB").click();
cy.contains("Version").click();
cy.contains("featureCompatibilityVersion");
});
});
import {
signInAsAdmin,
restore,
modal,
signInAsNormalUser,
} from "__support__/cypress";
function addMongoDatabase() {
cy.request("POST", "/api/database", {
engine: "mongo",
name: "MongoDB",
details: {
host: "localhost",
dbname: "admin",
port: 27017,
user: null,
pass: null,
authdb: null,
"additional-options": null,
"use-srv": false,
"tunnel-enabled": false,
},
auto_run_queries: true,
is_full_sync: true,
schedules: {
cache_field_values: {
schedule_day: null,
schedule_frame: null,
schedule_hour: 0,
schedule_type: "daily",
},
metadata_sync: {
schedule_day: null,
schedule_frame: null,
schedule_hour: null,
schedule_type: "hourly",
},
},
});
}
describe("mongodb > user > query", () => {
before(() => {
restore();
signInAsAdmin();
addMongoDatabase();
});
beforeEach(() => {
signInAsNormalUser();
});
it("can query a Mongo database as a user", () => {
cy.visit("/question/new");
cy.contains("Simple question").click();
cy.contains("MongoDB").click();
cy.contains("Version").click();
cy.contains("featureCompatibilityVersion");
});
it.only("can write a native MongoDB query", () => {
cy.visit("/question/new");
cy.contains("Native query").click();
cy.contains("MongoDB").click();
cy.get(".ace_content").type(`[ { $count: "Total" } ]`, {
parseSpecialCharSequences: false,
});
cy.get(".NativeQueryEditor .Icon-play").click();
cy.contains("1");
});
it("can save a native MongoDB query", () => {
cy.server();
cy.route("POST", "/api/card").as("createQuestion");
cy.visit("/question/new");
cy.contains("Native query").click();
cy.contains("MongoDB").click();
cy.get(".ace_content").type(`[ { $count: "Total" } ]`, {
parseSpecialCharSequences: false,
});
cy.get(".NativeQueryEditor .Icon-play").click();
cy.contains("1");
// Close the Ace editor because it interferes with the modal for some reason
cy.get(".Icon-contract").click();
cy.contains("Save").click();
modal()
.findByLabelText("Name")
.focus()
.type("mongo count");
modal()
.contains("button", "Save")
.should("not.be.disabled")
.click();
cy.wait("@createQuestion").then(({ status }) => {
expect(status).to.equal(202);
});
modal()
.contains("Not now")
.click();
cy.url().should("match", /\/question\/\d+$/);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment