Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[merge paused for India MVP] New cypress testing #470

Merged
merged 7 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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