Skip to content

Commit

Permalink
Merge pull request #470 from openclimatefix/new-cypress-testing
Browse files Browse the repository at this point in the history
[merge paused for India MVP] New cypress testing
  • Loading branch information
rachel-labri-tipton authored Feb 15, 2024
2 parents 8164485 + 1b36d58 commit 9a47351
Show file tree
Hide file tree
Showing 20 changed files with 5,039 additions and 4,083 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
node_modules
.turbo
build/**
dist/**
.next/**
12 changes: 9 additions & 3 deletions apps/nowcasting-app/components/charts/forecast-header/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export const ForecastHeadlineFigure: React.FC<{
: "dash:3xl:text-6xl dash:xl:text-5xl lg:text-3xl"
}`;
return (
<div className="flex gap-3 items-center m-auto h-10 dash:h-14 justify-between">
<div
data-test="pvlive-ocf-headline-figure"
className="flex gap-3 items-center m-auto h-10 dash:h-14 justify-between"
>
<div className="flex flex-1 self-center items-center justify-center">
<div className={`flex items-center ${textSizeClasses}`}>
<ForecastLabel
Expand Down Expand Up @@ -105,7 +108,10 @@ export const NextForecast: React.FC<{ pv: string; tip: string; time: string; col
color = yellow
}) => {
return (
<div className="flex gap-3 items-center m-auto h-10 dash:h-14 justify-between">
<div
data-test="forecast-label-tooltip"
className="flex gap-3 items-center m-auto h-10 dash:h-14 justify-between"
>
<ForecastLabel
className="dash:order-2"
tip={
Expand Down Expand Up @@ -158,7 +164,7 @@ const ForecastHeaderUI: React.FC<ForecastHeaderProps> = ({
forecastNextTimeOnly
}) => {
return (
<div className="flex content-between bg-ocf-gray-800 h-auto">
<div data-test="national-chart-header" className="flex content-between bg-ocf-gray-800 h-auto">
<div className="text-white dash:3xl:text-5xl dash:2xl:text-4xl dash:xl:text-3xl dash:tracking-wide lg:text-2xl md:text-lg text-base font-black m-auto ml-5 flex justify-evenly">
National
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/nowcasting-app/components/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const Map: FC<IMap> = ({
<div className="absolute top-0 left-0 z-10 p-4 min-w-[20rem] w-full">
{controlOverlay(map)}
</div>
<div ref={mapContainer} data-title={title} className="h-full w-full" />
<div ref={mapContainer} id={`Map-${title}`} data-title={title} className="h-full w-full" />
<div className="map-overlay top">{children}</div>
</div>
);
Expand Down
3 changes: 3 additions & 0 deletions apps/nowcasting-app/components/map/measuringUnit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const MeasuringUnit = ({
<button
onClick={(event) => onToggle(event, ActiveUnit.MW)}
disabled={isLoading}
id="MapButtonMW"
type="button"
className={`${buttonClasses} ${
activeUnit === ActiveUnit.MW
Expand All @@ -38,6 +39,7 @@ const MeasuringUnit = ({
<button
onClick={(event) => onToggle(event, ActiveUnit.percentage)}
disabled={isLoading}
id="MapButtonPercentage"
type="button"
className={`${buttonClasses} px-5 ${
activeUnit === ActiveUnit.percentage
Expand All @@ -51,6 +53,7 @@ const MeasuringUnit = ({
<button
onClick={(event) => onToggle(event, ActiveUnit.capacity)}
disabled={isLoading}
id="MapButtonCapacity"
type="button"
className={`${buttonClasses} ${
activeUnit === ActiveUnit.capacity ? "text-black bg-ocf-yellow" : "text-white bg-black"
Expand Down
29 changes: 29 additions & 0 deletions apps/nowcasting-app/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { defineConfig } from "cypress";
// Populate process.env with values from .env file
const dotenv = require("dotenv");

dotenv.config({ path: ".env.local" });
dotenv.config();

export default defineConfig({
env: {
auth0_username: process.env.NEXT_PUBLIC_AUTH0_USERNAME,
auth0_password: process.env.NEXT_PUBLIC_AUTH0_PASSWORD,
auth0_domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
auth0_audience: process.env.NEXT_PUBLIC_AUTH0_AUDIENCE,
auth0_scope: process.env.NEXT_PUBLIC_AUTH0_SCOPE,
auth0_client_id: process.env.NEXT_PUBLIC_AUTH0_CLIENTID,
auth0_client_secret: process.env.AUTH0_CLIENT_SECRET,
baseUrl: process.env.AUTH0_BASE_URL
},

chromeWebSecurity: false,
// ...rest of the Cypress project config
projectId: process.env.CYPRESS_PROJECT_ID,

e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
}
}
});
108 changes: 108 additions & 0 deletions apps/nowcasting-app/cypress/e2e/pvLatest.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
describe("Load the page", () => {
beforeEach(function () {
// cy.visit("http://localhost:3002/");
cy.loginToAuth0(Cypress.env("auth0_username"), Cypress.env("auth0_password"));
});
it("successfully loads", () => {
// Should now already be logged in and have a session cookie.
cy.visit("http://localhost:3002/");
// Ensure Auth0 has redirected us back to the local app.
cy.location("href").should("equal", "http://localhost:3002/");
});

////////////////////////////////
// GENERAL
////////////////////////////////
it("loads the main header elements", () => {
cy.visit("http://localhost:3002/");
cy.location("href").should("equal", "http://localhost:3002/");
// Header
cy.get("header").should("exist");
cy.get("header").should("be.visible");
// Nav
cy.get("header").should("contain", "PV Forecast");
cy.get("header").should("contain", "Solar Sites");
cy.get("header").should("contain", "Delta");
// Active page is highlighted
cy.get("header").contains("PV Forecast").should("have.class", "text-ocf-yellow");
cy.get("header").contains("Solar Sites").should("not.have.class", "text-ocf-yellow");
//
cy.get("header a[href='https://quartz.solar/']").should("exist");
cy.get("header a[href='https://quartz.solar/']").should("be.visible");
cy.get("header").should("contain", "powered by");
cy.get("header").contains("powered by").should("exist");
cy.get("header").contains("powered by").should("be.visible");
cy.get("header")
.contains("powered by")
.siblings("a")
.first()
.should("have.attr", "href", "https://www.openclimatefix.org/");
// Profile dropdown menu
cy.get("header #headlessui-menu-item-6").should("not.exist");
cy.get("header button").contains("Open user menu").should("exist");
cy.get("header button").contains("Open user menu").parent().click();
cy.get("header #headlessui-menu-item-6").should("exist");
cy.get("header #headlessui-menu-item-6").should("be.visible");
cy.get("header #headlessui-menu-item-6").should("contain", "4-hour forecast");
cy.get("header #headlessui-menu-item-7").should("contain", "Dashboard mode");
cy.get("header #headlessui-menu-item-8").should("contain", "Documentation");
cy.get("header #headlessui-menu-item-9").should("contain", "Contact");
cy.get("header #headlessui-menu-item-10").should("contain", "Give feedback");
cy.get("header #headlessui-menu-item-11").should("contain", "Sign out");
});

////////////////////////////////
// PV FORECAST
////////////////////////////////
// TODO: work out how to actually test the map elements
it.skip("test the PV Forecast map elements", () => {
cy.visit("http://localhost:3002/");
cy.location("href").should("equal", "http://localhost:3002/");
// TODO: Add tests for the PV Forecast page elements, probably with mocked data.
// national chart header
cy.get('[data-test="national-chart-header"]').contains("National").should("exist");
cy.get('[data-test="pv-ocf-forecast-headline-figure"]')
.contains("National")
.should("be.visible");
cy.get('[data-test="forecast-headline-figures"]').siblings().first().should("exist").click();
cy.get('[data-test="forecast-headline-figures"]').siblings().next().should("exist");
cy.get('[data-test="forecast-headline-figures"]').siblings().first().trigger("mouseover");
cy.get('[data-test="forecast-headline-figures"]')
.siblings()
.first()
.invoke("mouseover")
.should("contain", "PV Live / OCF Forecast");
cy.get('[data-test="forecast-headline-figures"]')
.siblings()
.first()
.trigger("mouseout")
.should("not.contain", "PV Live / OCF Forecast");
cy.get('[data-test="forecast-headline-figures"]')
.siblings()
.next()
.trigger("mouseover")
.contains("Next OCF Forecast");
cy.get('[data-test="forecast-headline-figures"]')
.siblings()
.next()
.trigger("mouseout")
.should("not.contain", "Next OCF Forecast");
// national chart play button
// play icon visible
// cy.get("data-test=national-chart-play-button").should("exist", "be.visible");
// // pause icon not visible
// // play icon visible
// cy.get("data-test=national-chart-play-button").should("exist", "be.visible").click();
// cy.get("data-test=national-chart-play-button").should("exist", "be.visible").click();

// national chart
// gsp chart header
// gsp chart
// gsp chart close button
// national pv chart legend check that elements are there
// legend select and deselect lines and check that they disappear and reappear
// national map with date and time
// national map with color scale
// national map buttons for capacity and generation
});
});
5 changes: 5 additions & 0 deletions apps/nowcasting-app/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
158 changes: 158 additions & 0 deletions apps/nowcasting-app/cypress/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/// <reference types="cypress" />

declare namespace Cypress {
import { authService } from "../src/machines/authMachine";
import { createTransactionService } from "../src/machines/createTransactionMachine";
import { publicTransactionService } from "../src/machines/publicTransactionsMachine";
import { contactsTransactionService } from "../src/machines/contactsTransactionsMachine";
import { personalTransactionService } from "../src/machines/personalTransactionsMachine";
import {
User,
BankAccount,
Like,
Comment,
Transaction,
BankTransfer,
Contact
} from "../src/models";

interface CustomWindow extends Window {
authService: typeof authService;
createTransactionService: typeof createTransactionService;
publicTransactionService: typeof publicTransactionService;
contactTransactionService: typeof contactsTransactionService;
personalTransactionService: typeof personalTransactionService;
}

type dbQueryArg = {
entity: string;
query: object | [object];
};

type LoginOptions = {
rememberUser: boolean;
};

interface Chainable {
/**
* Window object with additional properties used during test.
*/
window(options?: Partial<Loggable & Timeoutable>): Chainable<CustomWindow>;

/**
* Custom command to make taking Percy snapshots with full name formed from the test title + suffix easier
*/
visualSnapshot(maybeName?): Chainable<any>;

getBySel(dataTestAttribute: string, args?: any): Chainable<JQuery<HTMLElement>>;
getBySelLike(dataTestPrefixAttribute: string, args?: any): Chainable<JQuery<HTMLElement>>;

/**
* Cypress task for directly querying to the database within tests
*/
task(
event: "filter:database",
arg: dbQueryArg,
options?: Partial<Loggable & Timeoutable>
): Chainable<any[]>;

/**
* Cypress task for directly querying to the database within tests
*/
task(
event: "find:database",
arg?: any,
options?: Partial<Loggable & Timeoutable>
): Chainable<any>;

/**
* Find a single entity via database query
*/
database(operation: "find", entity: string, query?: object, log?: boolean): Chainable<any>;

/**
* Filter for data entities via database query
*/
database(operation: "filter", entity: string, query?: object, log?: boolean): Chainable<any>;

/**
* Fetch React component instance associated with received element subject
*/
reactComponent(): Chainable<any>;

/**
* Select data range within date range picker component
*/
pickDateRange(startDate: Date, endDate: Date): Chainable<void>;

/**
* Select transaction amount range
*/
setTransactionAmountRange(min: number, max: number): Chainable<any>;

/**
* Paginate to the next page in transaction infinite-scroll pagination view
*/
nextTransactionFeedPage(service: string, page: number): Chainable<any>;

/**
* Logs-in user by using UI
*/
login(username: string, password: string, loginOptions?: LoginOptions): void;

/**
* Logs-in user by using API request
*/
loginByApi(username: string, password?: string): Chainable<Response>;

/**
* Logs-in user by using Google API request
*/
loginByGoogleApi(): Chainable<Response>;

/**
* Logs-in user by using Okta API request
*/
loginByOktaApi(username: string, password?: string): Chainable<Response>;

/**
* Logs-in user by navigating to Okta tenant with cy.origin()
*/
loginByOkta(username: string, password: string): Chainable<Response>;

/**
* Logs in bypassing UI by triggering XState login event
*/
loginByXstate(username: string, password?: string): Chainable<any>;

/**
* Logs out via bypassing UI by triggering XState logout event
*/
logoutByXstate(): Chainable<string>;

/**
* Logs in via Auth0 login page
*/
loginToAuth0(username: string, password: string): Chainable<any>;

/**
* Switch current user by logging out current user and logging as user with specified username
*/
switchUserByXstate(username: string): Chainable<any>;

/**
* Create Transaction via bypassing UI and using XState createTransactionService
*/
createTransaction(payload): Chainable<any>;

/**
* Logs in to AWS Cognito via Amplify Auth API bypassing UI using Cypress Task
*/
loginByCognitoApi(username: string, password: string): Chainable<any>;

/**
* Logs in to AWS Cognito Federated via cy.origin()
*/
loginByCognito(username: string, password: string): Chainable<any>;
}
}
Loading

0 comments on commit 9a47351

Please sign in to comment.