diff --git a/README.md b/README.md
index c4247c54db..69def6a635 100644
--- a/README.md
+++ b/README.md
@@ -206,7 +206,35 @@ If you are unable to use Docker, please contact a core team member to get instru
# Testing
-## api
+## WebApp E2e tests
+
+When running Cypress E2E tests, it's recommended to use a dedicated test database. This helps keep your main development database free from testing data.
+
+### Setup
+
+1. Start the dedicated test database container with `docker compose --profile testing up testing-db`
+1. Run the `dev` migrations `npm run migrate:dev:db` from `packages/api`.
+
+- Make sure you copied `packages/api/.env.default` to `packages/api/.env`
+- Update in the `.env` file the next (if necessary):
+ - `NODE_ENV=test`
+ - `TEST_DATABASE_PORT=5434` (or any port that you use to run the postgresql instance or container)
+
+1. Run the `packages/webapp` (follow the instructions on how to do it)
+1. Go to `packages/end-to-end` directory.
+1. Install the E2e test dependencies`npm i`.
+
+### Execution
+
+1. Run the tests `$ npx cypress run`.
+
+As result videos and logs are going to be stored in the directories prompted by the shell.
+
+If you want to visualize the test execution, add the next flag: `--headed`
+
+1. `$ npx cypress run --headed`.
+
+## API Integration tests
To run [ESLint](https://eslint.org/) checks execute `npm run lint`
@@ -214,19 +242,27 @@ The [chai.js](https://www.chaijs.com/) and [jest](https://jestjs.io/) libraries
You'll want to confirm that you have an empty `test_farm` database (otherwise use your preferred database client to create one) before continuing with the following:
+### Setup
+
1. In a terminal, navigate to the `packages/api` folder.
-2. Execute `npm run migrate:testing:db` to set up the test database.
-3. Execute `npm test` to launch the tests. Or, to generate test coverage information, run `npm test -- --coverage .` and then see the `coverage/index.html` file.
+1. Execute `npm run migrate:testing:db` to set up the test database.
+
+### Execution
+
+1. Execute `npm test` to launch the tests.
+1. (Optionally) to generate test coverage information, run `npm test -- --coverage .` and then see the `coverage/index.html` file.
While the tests do attempt to clean up after themselves, it's a good idea to periodically use `psql` or your database client to `DROP` and `CREATE` the `test_farm` database, followed by the migrations from step 2 above.
-## webapp
+## WebApp testing
To run [ESLint](https://eslint.org/) checks execute `pnpm lint`
Since this is a mobile web application, webapp should be viewed in a mobile view in the browser.
-You can also test LiteFarm on your actual mobile device using the network adddress returned by `vite --host` when you start the webapp in development mode. To do this, also update `VITE_API_URL` in your `webapp/.env` file from localhost to that address (or your computer's network name) and the appropriate API port. Most of LiteFarm can be tested like this, but please note that Google SSO and some other functionality will not work over the local network.
+You can also test LiteFarm on your actual mobile device using the network adddress returned by `vite --host` when you start the webapp in development mode.
+To do this, also update `VITE_API_URL` in your `webapp/.env` file from localhost to that address (or your computer's network name) and the appropriate API port.
+Most of LiteFarm can be tested like this, but please note that Google SSO and some other functionality will not work over the local network.
# ngrok
diff --git a/docker-compose.yml b/docker-compose.yml
index 8668a02f52..4923ce617e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -17,6 +17,28 @@ services:
volumes:
- ./initdb.d:/docker-entrypoint-initdb.d
- postgres-data:/var/lib/postgresql/data
+
+ testing-db:
+ container_name: litefarm-test-db
+ image: postgres:13
+ ports:
+ - "5434:5432"
+ environment:
+ POSTGRES_DB: "test_farm"
+ POSTGRES_USER: "postgres"
+ POSTGRES_PASSWORD: "postgres"
+ POSTGRES_HOST: postgres
+ POSTGRES_PORT: 5432
+ volumes:
+ - ./initdb.d:/docker-entrypoint-initdb.d
+ healthcheck:
+ test: ["CMD", "pg_isready", "-U", "postgres"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+ profiles:
+ - testing
+
minio:
image: minio/minio:RELEASE.2023-06-09T07-32-12Z
restart: unless-stopped
@@ -60,8 +82,8 @@ services:
image: litefarm/node-awscli:latest
restart: unless-stopped
volumes:
- - ./packages/api:/packages/api
- - ./packages/webapp/public/locales:/packages/webapp/public/locales
+ - ./packages/api:/packages/api:Z
+ - ./packages/webapp/public/locales:/packages/webapp/public/locales:Z
- export-node-modules:/packages/api/node_modules
working_dir: /packages/api
entrypoint: ./dev.export.sh
diff --git a/packages/api/package.json b/packages/api/package.json
index 65f5a168a2..e0100514c1 100644
--- a/packages/api/package.json
+++ b/packages/api/package.json
@@ -12,11 +12,12 @@
"test-ci": "NODE_ENV=pipeline jest --runInBand --forceExit",
"test-end2end": "jest endToEnd",
"jest-w": "jest --watch",
- "start": "set NODE_ENV=development&& node --import @swc-node/register/esm-register src/server.ts",
+ "start": "set NODE_ENV=development node --import @swc-node/register/esm-register src/server.ts",
"start:prod": "node dist/app/src/server.js",
"build": "rimraf dist && tsc -p src/tsconfig.json",
"cp-uncompiled-to-dist": "rsync -a --include='*' --exclude='*.cjs' --exclude='*.js' --exclude='*.ts' ./src/templates ./dist/app/src && rsync -a --include='*' --exclude='*.cjs' --exclude='*.js' --exclude='*.ts' ./src/static ./dist/app/src/",
- "dev": "set NODE_ENV=development && node --inspect=0.0.0.0:9230 --watch --import @swc-node/register/esm-register src/server.ts",
+ "dev": "NODE_ENV=development node --inspect=0.0.0.0:9230 --watch --import @swc-node/register/esm-register src/server.ts",
+ "dev:test": "NODE_ENV=test node --inspect=0.0.0.0:9230 --watch --import @swc-node/register/esm-register src/server.ts",
"debug-email-template": "DEBUG=email-templates npm run dev",
"debug-i18n": "DEBUG=i18n:* npm run dev",
"production": "NODE_ENV=production npm run start:prod",
diff --git a/packages/end-to-end/cypress.config.js b/packages/end-to-end/cypress.config.js
index b52054b0f8..94562c8195 100644
--- a/packages/end-to-end/cypress.config.js
+++ b/packages/end-to-end/cypress.config.js
@@ -1,5 +1,9 @@
const { defineConfig } = require('cypress');
+const apiUrl = 'http://localhost:5001' // TODO: add default value or throw error if not configured.
+
+process.env.API_URL= apiUrl
+
module.exports = defineConfig({
projectId: 'wzcbom',
defaultCommandTimeout: 15 * 1000,
@@ -12,6 +16,9 @@ module.exports = defineConfig({
},
e2e: {
baseUrl: 'http://localhost:3000',
+ env: {
+ apiUrl,
+ },
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
setupNodeEvents(on, config) {
require('@cypress/code-coverage/task')(on, config);
diff --git a/packages/end-to-end/cypress/e2e/crops.js b/packages/end-to-end/cypress/e2e/crops.js
index 3b33a749f3..02f335e93f 100644
--- a/packages/end-to-end/cypress/e2e/crops.js
+++ b/packages/end-to-end/cypress/e2e/crops.js
@@ -13,123 +13,156 @@
* GNU General Public License for more details, see .
*/
-import moment from 'moment';
-import * as Selectors from '../support/selectorConstants.ts';
-import { loadTranslationsAndConfigureUserFarm } from '../support/utilities.js';
+// import moment from 'moment';
+// import * as Selectors from '../support/selectorConstants.ts';
+import {
+ createFarm,
+ createUser,
+ farmConsent,
+ farmToken,
+ initApi,
+ onboardFarm,
+ onboardRole,
+ organicCertifierSurvey,
+ ownerOperated,
+ releseBadge,
+ showedSpotlight,
+ userAuth
+} from '../support/api';
+import { loadTranslations } from '../support/utilities';
describe('Crops', () => {
- let translation;
- let crops;
-
- beforeEach(() => {
- loadTranslationsAndConfigureUserFarm({ additionalTranslation: 'crop_group' }).then(
- ([baseTranslation, additionalTranslation]) => {
- translation = baseTranslation;
- crops = additionalTranslation;
- },
- );
+ let ctx;
+
+ beforeEach(async () => {
+ initApi(Cypress.env('apiUrl'))
+ ctx = await createUser();
+ ctx.auth = await userAuth(ctx.user.email, ctx.password)
+
+ const [translation, crops] = await loadTranslations({
+ additionalTranslation: 'crop_group',
+ user: ctx.user,
+ });
+
+ ctx.translation = translation;
+ ctx.crops = crops;
+ ctx.farm = await createFarm(ctx.auth)
+
+ await onboardFarm(ctx.auth, ctx.farm.farm_id, ctx.user.user_id);
+ await onboardRole(ctx.auth, ctx.farm.farm_id, ctx.user.user_id, 2);
+ await ownerOperated(ctx.auth, ctx.farm.farm_id, ctx.user.user_id);
+ await farmConsent(ctx.auth, ctx.farm.farm_id, ctx.user.user_id, true);
+ await organicCertifierSurvey(ctx.auth, ctx.farm.farm_id, ctx.user.user_id);
+ await releseBadge(ctx.auth, ctx.farm.farm_id, ctx.user.user_id);
+ await showedSpotlight(ctx.auth, ctx.user.user_id);
+ ctx.farmToken = await farmToken(ctx.auth, ctx.farm.farm_id, ctx.user.user_id);
+ console.log(ctx)
});
it('should successfully add a crop variety and crop plan', () => {
- const uniqueSeed = Date.now().toString();
- const uniqueId = Cypress._.uniqueId(uniqueSeed);
-
- // Add a crop variety
- cy.contains(translation['MENU']['CROPS']).should('exist').click();
- cy.url().should('include', '/crop_catalogue');
-
- cy.get(Selectors.CROP_ADD_LINK).should('exist').and('not.be.disabled').click();
-
- cy.url().should('include', '/crop/new');
- cy.get(Selectors.CROP_CROP_NAME)
- .should('exist')
- .type('New Crop' + uniqueId);
- // cy.contains(translation['INVITE_USER']['CHOOSE_ROLE'])
- cy.getVisible(Selectors.REACT_SELECT)
- .find('input')
- .type(crops['CEREALS'] + '{enter}');
-
- cy.get(Selectors.CAN_BE_COVER_CROP).first().check({ force: true });
-
- cy.get(Selectors.CROP_SUBMIT).should('exist').and('not.be.disabled').click();
- cy.url().should('include', '/crop/new/add_crop_variety');
- cy.get(Selectors.CROP_VARIETY).should('exist').type('New Variety');
- cy.get(Selectors.CROP_SUPPLIER).should('exist').type('New Supplier');
- cy.get(Selectors.CROP_ANNUAL).should('exist').check({ force: true });
- cy.get(Selectors.VARIETY_SUBMIT).should('exist').and('not.be.disabled').click();
- cy.url().should('include', '/crop/new/add_crop_variety/compliance');
- cy.get(Selectors.COMPLIANCE_NEW_VARIETY_SAVE).should('exist').and('be.disabled');
- cy.get(Selectors.COMPLIANCE_SEED).eq(1).should('exist').check({ force: true });
- cy.get(Selectors.COMPLIANCE_SEED).eq(1).should('exist').check({ force: true });
- cy.get(Selectors.COMPLIANCE_SEED_AVAILABILITY).eq(1).should('exist').check({ force: true });
- cy.get(Selectors.COMPLIANCE_SEED_ENGINEERED).eq(0).should('exist').check({ force: true });
- cy.get(Selectors.COMPLIANCE_SEED_TREATED).eq(2).should('exist').check({ force: true });
- cy.get(Selectors.COMPLIANCE_NEW_VARIETY_SAVE).should('exist').and('not.be.disabled').click();
-
- // Check if spotlight was shown
- cy.window()
- .its('store')
- .invoke('getState')
- .its('entitiesReducer.showedSpotlightReducer.management_plan_creation')
- .then((managementPlanCreation) => {
- if (!managementPlanCreation) {
- // Checks if the value is false
- cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
- cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
- }
- });
-
- // Add Management Plan
- cy.contains(translation['CROP_DETAIL']['ADD_PLAN']).click();
- cy.get(Selectors.PLANTING_METHOD_GROUND_PLANTED).first().check();
- cy.get(Selectors.CROP_PLAN_SUBMIT).should('exist').and('not.be.disabled').click();
- cy.get(Selectors.CROP_PLAN_TRANSPLANT_SUBMIT).should('exist').and('not.be.disabled').click();
-
- const date = new Date();
- date.setDate(date.getDate() + 1);
- const getDateInputFormat = (date) => moment(date).format('YYYY-MM-DD');
- const dueDate = getDateInputFormat(date);
- cy.get(Selectors.CROP_PLAN_PLANT_DATE).should('exist').type(dueDate);
- cy.get(Selectors.CROP_PLAN_SEED_GERMINATION).should('exist').type('15');
- cy.get(Selectors.CROP_PLAN_PLANT_HARVEST).should('exist').type('30');
- cy.get(Selectors.PLANT_DATE_SUBMIT).should('exist').and('not.be.disabled').click();
-
- // Select field
- cy.contains('First Field').should('be.visible');
- // eslint-disable-next-line cypress/no-unnecessary-waiting
- cy.wait(500, { log: false });
- cy.get(Selectors.MAP_SELECT_LOCATION).click({ force: false });
- cy.get(Selectors.CROP_PLAN_LOCATION_SUBMIT).should('exist').and('not.be.disabled').click();
-
- // Planning Method
- cy.get(Selectors.PLANTING_METHOD_ROW).check();
- cy.get(Selectors.PLANTING_METHOD_SUBMIT).should('exist').and('not.be.disabled').click();
-
- // Row length
- cy.get(Selectors.ROW_METHOD_EQUAL_LENGTH).first().check();
-
- cy.get(Selectors.ROW_METHOD_ROWS).should('exist').type('15{enter}');
- cy.get(Selectors.ROW_METHOD_LENGTH).should('exist').type('15{enter}');
- cy.get(Selectors.ROW_METHOD_SPACING).should('exist').type('15{enter}');
- cy.contains(translation['MANAGEMENT_PLAN']['PLANT_SPACING']).click({ force: true });
- cy.get(Selectors.ROW_METHOD_YIELD).should('exist').type('15');
- cy.contains(translation['MANAGEMENT_PLAN']['PLANT_SPACING']).click({ force: true });
- cy.get(Selectors.ROW_METHOD_SUBMIT).should('exist').and('not.be.disabled').click();
-
- cy.get(Selectors.PLAN_GUIDANCE_SUBMIT).should('exist').and('not.be.disabled').click();
- cy.get(Selectors.CROP_PLAN_SAVE).should('exist').and('not.be.disabled').click();
-
- // Check if spotlight was shown
- cy.window()
- .its('store')
- .invoke('getState')
- .its('entitiesReducer.showedSpotlightReducer.crop_variety_detail')
- .then((managementPlanCreation) => {
- if (!managementPlanCreation) {
- // Checks if the value is false
- cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
- cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
- }
- });
+ cy.injectTokensToUI(ctx.auth.token, ctx.farmToken.farm_token);
+ cy.visit('/crop_catalogue')
+ cy.pause();
+
+ // const uniqueSeed = Date.now().toString();
+ // const uniqueId = Cypress._.uniqueId(uniqueSeed);
+
+ // // Add a crop variety
+ // cy.contains(translation['MENU']['CROPS']).should('exist').click();
+ // cy.url().should('include', '/crop_catalogue');
+
+ // cy.get(Selectors.CROP_ADD_LINK).should('exist').and('not.be.disabled').click();
+
+ // cy.url().should('include', '/crop/new');
+ // cy.get(Selectors.CROP_CROP_NAME)
+ // .should('exist')
+ // .type('New Crop' + uniqueId);
+ // // cy.contains(translation['INVITE_USER']['CHOOSE_ROLE'])
+ // cy.getVisible(Selectors.REACT_SELECT)
+ // .find('input')
+ // .type(crops['CEREALS'] + '{enter}');
+
+ // cy.get(Selectors.CAN_BE_COVER_CROP).first().check({ force: true });
+
+ // cy.get(Selectors.CROP_SUBMIT).should('exist').and('not.be.disabled').click();
+ // cy.url().should('include', '/crop/new/add_crop_variety');
+ // cy.get(Selectors.CROP_VARIETY).should('exist').type('New Variety');
+ // cy.get(Selectors.CROP_SUPPLIER).should('exist').type('New Supplier');
+ // cy.get(Selectors.CROP_ANNUAL).should('exist').check({ force: true });
+ // cy.get(Selectors.VARIETY_SUBMIT).should('exist').and('not.be.disabled').click();
+ // cy.url().should('include', '/crop/new/add_crop_variety/compliance');
+ // cy.get(Selectors.COMPLIANCE_NEW_VARIETY_SAVE).should('exist').and('be.disabled');
+ // cy.get(Selectors.COMPLIANCE_SEED).eq(1).should('exist').check({ force: true });
+ // cy.get(Selectors.COMPLIANCE_SEED).eq(1).should('exist').check({ force: true });
+ // cy.get(Selectors.COMPLIANCE_SEED_AVAILABILITY).eq(1).should('exist').check({ force: true });
+ // cy.get(Selectors.COMPLIANCE_SEED_ENGINEERED).eq(0).should('exist').check({ force: true });
+ // cy.get(Selectors.COMPLIANCE_SEED_TREATED).eq(2).should('exist').check({ force: true });
+ // cy.get(Selectors.COMPLIANCE_NEW_VARIETY_SAVE).should('exist').and('not.be.disabled').click();
+
+ // // Check if spotlight was shown
+ // cy.window()
+ // .its('store')
+ // .invoke('getState')
+ // .its('entitiesReducer.showedSpotlightReducer.management_plan_creation')
+ // .then((managementPlanCreation) => {
+ // if (!managementPlanCreation) {
+ // // Checks if the value is false
+ // cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
+ // cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
+ // }
+ // });
+
+ // // Add Management Plan
+ // cy.contains(translation['CROP_DETAIL']['ADD_PLAN']).click();
+ // cy.get(Selectors.PLANTING_METHOD_GROUND_PLANTED).first().check();
+ // cy.get(Selectors.CROP_PLAN_SUBMIT).should('exist').and('not.be.disabled').click();
+ // cy.get(Selectors.CROP_PLAN_TRANSPLANT_SUBMIT).should('exist').and('not.be.disabled').click();
+
+ // const date = new Date();
+ // date.setDate(date.getDate() + 1);
+ // const getDateInputFormat = (date) => moment(date).format('YYYY-MM-DD');
+ // const dueDate = getDateInputFormat(date);
+ // cy.get(Selectors.CROP_PLAN_PLANT_DATE).should('exist').type(dueDate);
+ // cy.get(Selectors.CROP_PLAN_SEED_GERMINATION).should('exist').type('15');
+ // cy.get(Selectors.CROP_PLAN_PLANT_HARVEST).should('exist').type('30');
+ // cy.get(Selectors.PLANT_DATE_SUBMIT).should('exist').and('not.be.disabled').click();
+
+ // // Select field
+ // cy.contains('First Field').should('be.visible');
+ // // eslint-disable-next-line cypress/no-unnecessary-waiting
+ // cy.wait(500, { log: false });
+ // cy.get(Selectors.MAP_SELECT_LOCATION).click({ force: false });
+ // cy.get(Selectors.CROP_PLAN_LOCATION_SUBMIT).should('exist').and('not.be.disabled').click();
+
+ // // Planning Method
+ // cy.get(Selectors.PLANTING_METHOD_ROW).check();
+ // cy.get(Selectors.PLANTING_METHOD_SUBMIT).should('exist').and('not.be.disabled').click();
+
+ // // Row length
+ // cy.get(Selectors.ROW_METHOD_EQUAL_LENGTH).first().check();
+
+ // cy.get(Selectors.ROW_METHOD_ROWS).should('exist').type('15{enter}');
+ // cy.get(Selectors.ROW_METHOD_LENGTH).should('exist').type('15{enter}');
+ // cy.get(Selectors.ROW_METHOD_SPACING).should('exist').type('15{enter}');
+ // cy.contains(translation['MANAGEMENT_PLAN']['PLANT_SPACING']).click({ force: true });
+ // cy.get(Selectors.ROW_METHOD_YIELD).should('exist').type('15');
+ // cy.contains(translation['MANAGEMENT_PLAN']['PLANT_SPACING']).click({ force: true });
+ // cy.get(Selectors.ROW_METHOD_SUBMIT).should('exist').and('not.be.disabled').click();
+
+ // cy.get(Selectors.PLAN_GUIDANCE_SUBMIT).should('exist').and('not.be.disabled').click();
+ // cy.get(Selectors.CROP_PLAN_SAVE).should('exist').and('not.be.disabled').click();
+
+ // // Check if spotlight was shown
+ // cy.window()
+ // .its('store')
+ // .invoke('getState')
+ // .its('entitiesReducer.showedSpotlightReducer.crop_variety_detail')
+ // .then((managementPlanCreation) => {
+ // if (!managementPlanCreation) {
+ // // Checks if the value is false
+ // cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
+ // cy.get(Selectors.SPOTLIGHT_NEXT).should('exist').and('not.be.disabled').click();
+ // }
+ // });
});
});
diff --git a/packages/end-to-end/cypress/fixtures/test.fixture.ts b/packages/end-to-end/cypress/fixtures/test.fixture.ts
new file mode 100644
index 0000000000..7d24eade3c
--- /dev/null
+++ b/packages/end-to-end/cypress/fixtures/test.fixture.ts
@@ -0,0 +1,25 @@
+const date = new Date().toISOString();
+
+export const onboarding = {
+ step_one: true,
+ step_one_end: date,
+ step_two: true,
+ step_two_end: date,
+ step_three: true,
+ step_three_end: date,
+ step_four: true,
+ step_four_end: date,
+ step_five: true,
+ step_five_end: date,
+}
+
+export const farm = {
+ farm_name: 'test farm',
+ address: 'Tarahumara 390, Francisco Villa, 96566 Coatzacoalcos, Ver., Mexico',
+ grid_points: {
+ lat: 18.1215184,
+ lng: -94.46313719999999,
+ },
+ country: 'MX',
+};
+
diff --git a/packages/end-to-end/cypress/support/api.ts b/packages/end-to-end/cypress/support/api.ts
new file mode 100644
index 0000000000..18b621be5a
--- /dev/null
+++ b/packages/end-to-end/cypress/support/api.ts
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2026 LiteFarm.org
+ * This file is part of LiteFarm.
+ *
+ * LiteFarm is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LiteFarm is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details, see .
+ */
+
+/**
+* Anti-cypress file, don't add their nonesense Chainable here.
+* Try to keep dependencies at minimum, keep in mind this file
+* runs in the UI, therefore no nodejs package can be used.
+**/
+import { farm as fixtureFarm, onboarding } from '../fixtures/test.fixture'
+
+const PASSWORD = 'Password123!';
+let hostname;
+
+export const initApi = (url) => hostname = url;
+
+export const createUser = async (overrides = {}) => {
+ const payload = {
+ first_name: 'Test',
+ last_name: 'User',
+ email: `test-${Date.now()}@example.com`,
+ password: PASSWORD,
+ language_preference: 'en',
+ ...overrides,
+ };
+
+ const response = await fetch(`${hostname}/user`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+ return {password: payload.password, ...await response.json()}
+};
+
+export const userAuth = async (email, password = PASSWORD) => {
+ const payload = {
+ user: {
+ email,
+ password,
+ },
+ screenSize: {
+ screen_width: 2506,
+ screen_height: 411,
+ },
+ };
+
+ const response = await fetch(`${hostname}/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+ const { id_token } = await response.json();
+ return { token: id_token, authHeader: { Authorization: `Bearer ${ id_token }`, } };
+}
+
+export const createFarm = async (auth, farm = fixtureFarm) => {
+ const response = await fetch(`${hostname}/farm`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...auth.authHeader,
+ },
+
+ body: JSON.stringify(farm),
+ });
+
+ return await response.json();
+}
+
+export const onboardFarm = async(auth, farmId, userId) => {
+ await fetch(`${hostname}/user_farm/onboarding/farm/${farmId}/user/${userId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify(onboarding),
+ });
+}
+
+export const onboardRole = async(auth, farmId, userId, roleId = 2) => {
+ await fetch(`${hostname}/user_farm/role/farm/${farmId}/user/${userId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ user_id: userId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify({ role_id: roleId }),
+ });
+}
+
+export const ownerOperated = async(auth, farmId, userId) => {
+ console.log(`${hostname}/farm/owner_operated/${farmId}`)
+ const response = await fetch(`${hostname}/farm/owner_operated/${farmId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ user_id: userId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify({ owner_operated: true }),
+ });
+
+ return await response.json();
+}
+export const farmConsent = async(auth, farmId, userId, hasConsent = false) => {
+ await fetch(`${hostname}/user_farm/consent/farm/${farmId}/user/${userId}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ user_id: userId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify({ has_consent: hasConsent, consent_version: '7.1' }),
+ });
+}
+
+export const organicCertifierSurvey = async (auth, farmId, userId) => {
+ const response = await fetch(`${hostname}/organic_certifier_survey`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ user_id: userId,
+ ...auth.authHeader,
+ },
+
+ body: JSON.stringify({
+ certification_id:null,
+ certifier_id:null,
+ farm_id:farmId,
+ interested:false,
+ requested_certification:null,
+ requested_certifier:null
+ }),
+ });
+
+ return await response.json();
+}
+
+export const releseBadge = async(auth, farmId, userId) => {
+ await fetch(`${hostname}/release_badge`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ farm_id: farmId,
+ user_id: userId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify({app_version:"3.12.0"}),
+ });
+}
+export const showedSpotlight = async(auth, userId) => {
+ await fetch(`${hostname}/showed_spotlight`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ user_id: userId,
+ ...auth.authHeader,
+ },
+ body: JSON.stringify({
+ notification:true,
+ notification_end:"2026-07-01T08:21:04.769Z",
+ navigation:true,
+ navigation_end:"2026-07-01T08:21:04.776Z"
+ }),
+ });
+}
+
+export const farmToken = async(auth, farmId, userId) => {
+ console.log(`${hostname}/farm_token/farm/${farmId}`)
+ const response = await fetch(`${hostname}/farm_token/farm/${farmId}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ user_id: userId,
+ farm_id: farmId,
+ ...auth.authHeader,
+ }
+ });
+
+ return await response.json();
+}
+
diff --git a/packages/end-to-end/cypress/support/commands.js b/packages/end-to-end/cypress/support/commands.js
index 56a0dec063..307b1a4c16 100644
--- a/packages/end-to-end/cypress/support/commands.js
+++ b/packages/end-to-end/cypress/support/commands.js
@@ -95,6 +95,7 @@ Cypress.Commands.add(
},
);
+
const addFarm = (farmName, location) => {
cy.intercept('GET', '**/maps.googleapis.com/maps/api/js/GeocodeService.*').as(
'googleMapGeocodeCall',
@@ -253,3 +254,13 @@ const acceptSlideMenuSpotlights = (crop_menu_name) => {
.and('not.be.disabled')
.click();
};
+
+Cypress.Commands.add('injectTokensToUI', (user, farm) => {
+ cy.visit('/', {
+ onBeforeLoad(win) {
+ win.localStorage.setItem('id_token', user);
+ win.localStorage.setItem('farm_token', farm);
+ },
+ });
+});
+
diff --git a/packages/end-to-end/cypress/support/index.d.ts b/packages/end-to-end/cypress/support/index.d.ts
new file mode 100644
index 0000000000..fa3620755c
--- /dev/null
+++ b/packages/end-to-end/cypress/support/index.d.ts
@@ -0,0 +1,10 @@
+declare namespace Cypress {
+ interface Chainable {
+ createUser(overrides?: object): Chainable;
+
+ apiLogin(email: string): Chainable;
+
+ createUserAndLogin(overrides?: object): Chainable;
+ injectTokensToUI(user: string, token: string): Chainable;
+ }
+}
diff --git a/packages/end-to-end/cypress/support/utilities.js b/packages/end-to-end/cypress/support/utilities.js
index 9d9500fe81..8e17d21b1d 100644
--- a/packages/end-to-end/cypress/support/utilities.js
+++ b/packages/end-to-end/cypress/support/utilities.js
@@ -12,34 +12,20 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see .
*/
+export async function loadTranslations({ additionalTranslation, user }) {
+ const lang = user.language_preference;
-export const loadTranslationsAndConfigureUserFarm = ({ additionalTranslation }) => {
- return cy.fixture('e2e-test-users.json').then((loadedUsers) => {
- const users = loadedUsers;
- const user = users[Cypress.env('USER')];
+ const [translation, additional] = await Promise.all([
+ fetch(`/locales/${lang}/translation.json`).then((r) => {
+ if (!r.ok) throw new Error(`Failed to load translation.json`);
+ return r.json();
+ }),
+ fetch(`/locales/${lang}/${additionalTranslation}.json`).then((r) => {
+ if (!r.ok) throw new Error(`Failed to load ${additionalTranslation}.json`);
+ return r.json();
+ }),
+ ]);
- return cy
- .fixture('../../../webapp/public/locales/' + user.locale + '/translation.json')
- .then((translation) => {
- cy.visit('/');
+ return [translation, additional];
+}
- cy.loginOrCreateAccount(
- user.email,
- user.password,
- user.name,
- user.language,
- translation['MENU']['CROPS'],
- translation['MENU']['MAP'],
- translation['FARM_MAP']['MAP_FILTER']['GARDEN'],
- );
-
- return cy
- .fixture(
- '../../../webapp/public/locales/' + user.locale + `/${additionalTranslation}.json`,
- )
- .then((additionalTranslationData) => {
- return [translation, additionalTranslationData];
- });
- });
- });
-};
diff --git a/packages/end-to-end/package-lock.json b/packages/end-to-end/package-lock.json
index e86bd26c3f..6d4164bc02 100644
--- a/packages/end-to-end/package-lock.json
+++ b/packages/end-to-end/package-lock.json
@@ -20,7 +20,8 @@
"eslint-plugin-prettier": "^4.2.1",
"lint-staged": "^13.2.3",
"moment": "^2.29.4",
- "prettier": "^2.8.8"
+ "prettier": "^2.8.8",
+ "typescript": "^6.0.3"
},
"engines": {
"node": "22.21"
@@ -7346,6 +7347,20 @@
"is-typedarray": "^1.0.0"
}
},
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -13112,6 +13127,12 @@
"is-typedarray": "^1.0.0"
}
},
+ "typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true
+ },
"unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
diff --git a/packages/end-to-end/package.json b/packages/end-to-end/package.json
index f63b8279e1..06f6456d0a 100644
--- a/packages/end-to-end/package.json
+++ b/packages/end-to-end/package.json
@@ -22,7 +22,8 @@
"eslint-plugin-prettier": "^4.2.1",
"lint-staged": "^13.2.3",
"moment": "^2.29.4",
- "prettier": "^2.8.8"
+ "prettier": "^2.8.8",
+ "typescript": "^6.0.3"
},
"dependencies": {
"eslint-plugin-cypress": "^2.13.3"
diff --git a/packages/end-to-end/tsconfig.json b/packages/end-to-end/tsconfig.json
index bb2d20029a..c95003e45b 100644
--- a/packages/end-to-end/tsconfig.json
+++ b/packages/end-to-end/tsconfig.json
@@ -2,8 +2,11 @@
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
+ "allowJs": true,
+ "checkJs": true,
"types": ["cypress", "node"],
- "baseUrl": "./cypress"
+ "baseUrl": "./cypress",
+ "ignoreDeprecations": "6.0"
},
- "include": ["**/*.ts"]
+ "include": ["cypress/**/*.js", "cypress/**/*.ts", "cypress/**/*.d.ts", "cypress.config.js"]
}