From d78cd4a4c6a3aa7f3f761567ae06b4fcad90e50c Mon Sep 17 00:00:00 2001 From: Nicolas Pierre-charles Date: Mon, 13 May 2024 15:56:03 +0200 Subject: [PATCH] feat(generator): update e2e test script ref: MANAGER-14323 Signed-off-by: Nicolas Pierre-charles --- .../api/services-template-get.ts.hbs | 41 +++++----- .../default/home.test.ts.hbs | 6 -- .../index-api-v2-pagination-cursor.tsx.hbs | 10 ++- .../index-api-v6-pagination-step.tsx.hbs | 66 ++++++++------- .../listing/index.tsx.hbs | 62 -------------- .../listing/listing.test.ts.hbs | 6 -- .../onboarding/onboarding.test.ts.hbs | 10 --- .../onboarding/Messages_fr_FR.json.hbs | 2 +- packages/manager/core/generator/app/index.js | 81 ++++++------------- .../core/generator/app/templates/cucumber.js | 20 +++++ .../app/templates/e2e/features/error.feature | 12 +++ .../templates/e2e/features/onboarding.feature | 7 ++ .../e2e/step-definitions/error.step.ts.hbs | 53 ++++++++++++ .../e2e/step-definitions/onboarding.step.ts | 32 ++++++++ .../app/templates/e2e/utils/constants.ts | 7 ++ .../app/templates/e2e/utils/index.tsx | 2 + .../app/templates/e2e/utils/network.ts | 28 +++++++ .../templates/mocks/example/example-data.json | 11 +++ .../app/templates/mocks/example/example.ts | 30 +++++++ .../generator/app/templates/mocks/index.ts | 1 + .../generator/app/templates/package.json.hbs | 8 +- .../app/templates/playwright.config.ts | 5 ++ .../app/templates/src/routes/routes.tsx.hbs | 4 +- .../templates/src/{{appName}}.config.ts.hbs | 2 - .../generator/app/templates/tsconfig.json | 3 +- .../app/templates/tsconfig.test.json | 6 ++ .../core/generator/app/universes.constant.js | 2 +- scripts/run-playwright-bdd.js | 29 ++++--- 28 files changed, 322 insertions(+), 224 deletions(-) delete mode 100644 packages/manager/core/generator/app/conditional-templates/default/home.test.ts.hbs delete mode 100644 packages/manager/core/generator/app/conditional-templates/listing/index.tsx.hbs delete mode 100644 packages/manager/core/generator/app/conditional-templates/listing/listing.test.ts.hbs delete mode 100644 packages/manager/core/generator/app/conditional-templates/onboarding/onboarding.test.ts.hbs create mode 100644 packages/manager/core/generator/app/templates/cucumber.js create mode 100644 packages/manager/core/generator/app/templates/e2e/features/error.feature create mode 100644 packages/manager/core/generator/app/templates/e2e/features/onboarding.feature create mode 100644 packages/manager/core/generator/app/templates/e2e/step-definitions/error.step.ts.hbs create mode 100644 packages/manager/core/generator/app/templates/e2e/step-definitions/onboarding.step.ts create mode 100644 packages/manager/core/generator/app/templates/e2e/utils/constants.ts create mode 100644 packages/manager/core/generator/app/templates/e2e/utils/index.tsx create mode 100644 packages/manager/core/generator/app/templates/e2e/utils/network.ts create mode 100644 packages/manager/core/generator/app/templates/mocks/example/example-data.json create mode 100644 packages/manager/core/generator/app/templates/mocks/example/example.ts create mode 100644 packages/manager/core/generator/app/templates/mocks/index.ts create mode 100644 packages/manager/core/generator/app/templates/tsconfig.test.json diff --git a/packages/manager/core/generator/app/conditional-templates/api/services-template-get.ts.hbs b/packages/manager/core/generator/app/conditional-templates/api/services-template-get.ts.hbs index 883c16e8678c..e9dbb9384d6d 100644 --- a/packages/manager/core/generator/app/conditional-templates/api/services-template-get.ts.hbs +++ b/packages/manager/core/generator/app/conditional-templates/api/services-template-get.ts.hbs @@ -1,4 +1,5 @@ -import { fetchIcebergV2, fetchIcebergV6, apiClient } from '@ovh-ux/manager-core-api'; +import { {{#if this.isApiV2}}fetchIcebergV2, {{/if}}{{#if this.isApiV6}}fetchIcebergV6, {{/if}}apiClient } from '@ovh-ux/manager-core-api'; + {{!-- {{#if unknownTypeList}} {{#each unknownTypeList}} @@ -33,18 +34,15 @@ export const {{this.functionName}} = async ({{#if this.params}}params: {{pascalC * Get listing with iceberg V6 */ export const getListingIcebergV6 = async ({ {{#if this.isPCI }}projectId, {{/if}}pageSize, page }: { {{#if this.isPCI }}projectId: string, {{/if}}pageSize: number, page: number }) => { - try { - const List = await fetchIcebergV6({ - route: `{{#if this.isPCI }}{{this.mainApiPathPci}}{{else}}{{this.mainApiPath}}{{/if}}`, - pageSize, - page - }).then( - ({ data, status, totalCount }) => ({ data, status, totalCount }), - ); - return List; - } catch (error) { - return null; + const { data, status, totalCount } = await fetchIcebergV6({ + route: `{{#if this.isPCI }}{{this.mainApiPathPci}}{{else}}{{this.mainApiPath}}{{/if}}`, + pageSize, + page + }); + if (status > 400) { + throw new Error(); } + return { data, status, totalCount }; }; {{/if}} @@ -54,18 +52,15 @@ export const getListingIcebergV6 = async ({ {{#if this.isPCI }}projectId, {{/if} */ export const getListingIcebergV2 = async ({ {{#if this.isPCI }}projectId, {{/if}}pageSize, cursor }: { {{#if this.isPCI }}projectId: string, {{/if}}pageSize: number, cursor?: string }) => { - try { - const List = await fetchIcebergV2({ - route: `{{#if this.isPCI }}{{this.mainApiPathPci}}{{else}}{{this.mainApiPath}}{{/if}}`, - pageSize, - cursor - }).then( - ({ data, status, cursorNext }) => ({ data, status, cursorNext }), - ); - return List; - } catch (error) { - return null; + const { data, status, cursorNext } = await fetchIcebergV2({ + route: `{{#if this.isPCI }}{{this.mainApiPathPci}}{{else}}{{this.mainApiPath}}{{/if}}`, + pageSize, + cursor + }); + if (status > 400) { + throw new Error(); } + return { data, status, cursorNext }; }; {{/if}} diff --git a/packages/manager/core/generator/app/conditional-templates/default/home.test.ts.hbs b/packages/manager/core/generator/app/conditional-templates/default/home.test.ts.hbs deleted file mode 100644 index c5bdfb4d88d6..000000000000 --- a/packages/manager/core/generator/app/conditional-templates/default/home.test.ts.hbs +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; -import '@playwright-helpers/login'; - -test('Start your application text by default', async ({ page }) => { - await expect(page.locator('body')).toContainText('Start your application'); -}); diff --git a/packages/manager/core/generator/app/conditional-templates/listing/index-api-v2-pagination-cursor.tsx.hbs b/packages/manager/core/generator/app/conditional-templates/listing/index-api-v2-pagination-cursor.tsx.hbs index 084d30ee5145..76d7f49144c0 100644 --- a/packages/manager/core/generator/app/conditional-templates/listing/index-api-v2-pagination-cursor.tsx.hbs +++ b/packages/manager/core/generator/app/conditional-templates/listing/index-api-v2-pagination-cursor.tsx.hbs @@ -1,6 +1,6 @@ import React, { useState, useEffect, } from 'react'; import { useTranslation } from 'react-i18next'; -import { Navigate, {{#if isPCI }}useParams, {{/if}} useNavigate, useLocation } from 'react-router-dom'; +import { {{#if isPCI }}useParams, {{/if}} useNavigate, useLocation } from 'react-router-dom'; import { useInfiniteQuery } from '@tanstack/react-query'; import { OsdsButton, OsdsLink } from '@ovhcloud/ods-components/react'; @@ -20,6 +20,7 @@ import ErrorBanner from '@/components/Error/Error'; import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; import appConfig from '@/{{appName}}.config'; +import { urls } from '@/routes/routes.constant'; export default function Listing() { const { t } = useTranslation('listing'); @@ -44,6 +45,7 @@ export default function Listing() { queryKey: [`servicesListingIceberg`], queryFn: ({ pageParam }) => getListingIcebergV2({ {{#if isPCI }}projectId, {{/if}}pageSize, cursor: pageParam }), staleTime: Infinity, + retry: false, getNextPageParam: (lastPage) => lastPage.cursorNext as any, }); @@ -54,7 +56,9 @@ export default function Listing() { }; useEffect(() => { - if (status === 'success' && data?.pages.length > 0 && !flattenData) { + if (status === 'success' && data?.pages[0].data.length === 0) { + navigate(urls.onboarding); + } else if (status === 'success' && data?.pages.length > 0 && !flattenData) { const tmp = Object.keys(data?.pages[0].data[0]) .filter((element) => element !== 'iam') .map((element) => ({ @@ -98,8 +102,6 @@ export default function Listing() { return (
) } - if (data?.length === 0) return ; - return ( <>
diff --git a/packages/manager/core/generator/app/conditional-templates/listing/index-api-v6-pagination-step.tsx.hbs b/packages/manager/core/generator/app/conditional-templates/listing/index-api-v6-pagination-step.tsx.hbs index b23e0de5fa08..052c10de9fea 100644 --- a/packages/manager/core/generator/app/conditional-templates/listing/index-api-v6-pagination-step.tsx.hbs +++ b/packages/manager/core/generator/app/conditional-templates/listing/index-api-v6-pagination-step.tsx.hbs @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Navigate,{{#if isPCI }} useParams, {{/if}} useNavigate, useLocation } from 'react-router-dom'; +import { {{#if isPCI }} useParams, {{/if}} useNavigate, useLocation } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { OsdsLink } from '@ovhcloud/ods-components/react'; @@ -19,6 +19,7 @@ import ErrorBanner from '@/components/Error/Error'; import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; import appConfig from '@/{{appName}}.config'; +import { urls } from '@/routes/routes.constant'; export default function Listing() { const myConfig = appConfig; @@ -41,6 +42,7 @@ export default function Listing() { const { data, isError, error, isLoading, status }: any = useQuery({ queryKey: [`servicesListingIceberg-${pageIndex + 1}-${pageSize}`], queryFn: () => getListingIcebergV6({ {{#if isPCI }}projectId, {{/if}}pageSize, page: pageIndex + 1 }), + retry: false, staleTime: Infinity, enabled: true, }); @@ -53,34 +55,38 @@ export default function Listing() { useEffect(() => { if (status === 'success' && data?.data) { - setRes(data?.data); - const newColumns = Object.keys(data?.data[0]) - .filter((element) => element !== 'iam') - .map((element) => ({ - id: element, - header: element, - label: element, - accessorKey: element, - cell: (props: any) => { - const label = props[element] as string; - if (typeof label === 'string' || typeof label === 'number') { - if (serviceKey === element) - return ( - - navigateToDashabord(label)} - > - {label} - - - ); - return {label}; - } - return
-
; - }, - })); - setColumns(newColumns); + if (data?.data.length === 0) { + navigate(urls.onboarding); + } else { + setRes(data?.data); + const newColumns = Object.keys(data?.data[0]) + .filter((element) => element !== 'iam') + .map((element) => ({ + id: element, + header: element, + label: element, + accessorKey: element, + cell: (props: any) => { + const label = props[element] as string; + if (typeof label === 'string' || typeof label === 'number') { + if (serviceKey === element) + return ( + + navigateToDashabord(label)} + > + {label} + + + ); + return {label}; + } + return
-
; + }, + })); + setColumns(newColumns); + } } }, [data?.data]); @@ -92,8 +98,6 @@ export default function Listing() { return (
) } - if (data?.length === 0) return ; - return ( <>
diff --git a/packages/manager/core/generator/app/conditional-templates/listing/index.tsx.hbs b/packages/manager/core/generator/app/conditional-templates/listing/index.tsx.hbs deleted file mode 100644 index b47901f7311e..000000000000 --- a/packages/manager/core/generator/app/conditional-templates/listing/index.tsx.hbs +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { Navigate, Outlet} from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; -import { getListingIceberg } from '@/api'; -import Datagrid from '@/components/layout-helpers/Listing/dataGrid'; -import Loading from '@/components/Loading/Loading'; -import appConfig from '@/{{this.appName}}.config'; -import Breadcrumb from "@/components/Breadcrumb/Breadcrumb"; - -import Errors from '@/components/Error/Error'; - -type ServiceData = { - serviceName: string; -}; - -const ServiceList: React.FC<{ data: ServiceData[] }> = ({ data }) => { - const count = data?.length; - - if (count === 0) return ; - - return (count === 1) - ? ( - <> - - - - ) - : ( - <> -

{{appName}}

- - - ); -} - -export default function Listing() { - const { data, isError, error, isLoading }: any = useQuery( - { queryKey: ['servicesListingIceberg'], queryFn: getListingIceberg, staleTime: Infinity, }, - ); - - - if (isError) { - return - } - - if (isLoading) { - return (
) - } - - - return ( - <> - - - - ); -} diff --git a/packages/manager/core/generator/app/conditional-templates/listing/listing.test.ts.hbs b/packages/manager/core/generator/app/conditional-templates/listing/listing.test.ts.hbs deleted file mode 100644 index 25f05e6c8526..000000000000 --- a/packages/manager/core/generator/app/conditional-templates/listing/listing.test.ts.hbs +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; -import '@playwright-helpers/login'; - -test('Listing page should contain Services list', async ({ page }) => { - await expect(page.locator('h2')).toContainText('Services list'); -}); diff --git a/packages/manager/core/generator/app/conditional-templates/onboarding/onboarding.test.ts.hbs b/packages/manager/core/generator/app/conditional-templates/onboarding/onboarding.test.ts.hbs deleted file mode 100644 index b9711fa0c8a0..000000000000 --- a/packages/manager/core/generator/app/conditional-templates/onboarding/onboarding.test.ts.hbs +++ /dev/null @@ -1,10 +0,0 @@ -import { expect, test } from '@playwright/test'; -import '@playwright-helpers/login'; -import config from '@playwright-helpers/config'; - -test('Onboarding page should be present', async ({ page }) => { - const onboardingPath = '/#/onboarding'; - const absoluteUrl = new URL(onboardingPath, config.appUrl).href; - await page.goto(absoluteUrl); - await expect(page.locator('h1')).toContainText('Onboarding page'); -}); diff --git a/packages/manager/core/generator/app/conditional-translations/onboarding/Messages_fr_FR.json.hbs b/packages/manager/core/generator/app/conditional-translations/onboarding/Messages_fr_FR.json.hbs index 5a6331a49396..48a809de3e7d 100644 --- a/packages/manager/core/generator/app/conditional-translations/onboarding/Messages_fr_FR.json.hbs +++ b/packages/manager/core/generator/app/conditional-translations/onboarding/Messages_fr_FR.json.hbs @@ -9,5 +9,5 @@ "guide2Title": "Monter votre NAS via un partage NFS", "guide2Description": "Découvrez comment monter un NAS via un partage NFS", "guide3Title": "Monter votre NAS sur Windows Server via CIFS", - "guide3Description": "Découvrez comment monter un NAS sur Windows Server via le protocole CIFS", + "guide3Description": "Découvrez comment monter un NAS sur Windows Server via le protocole CIFS" } diff --git a/packages/manager/core/generator/app/index.js b/packages/manager/core/generator/app/index.js index 5f49591dc0db..59bfee814779 100644 --- a/packages/manager/core/generator/app/index.js +++ b/packages/manager/core/generator/app/index.js @@ -93,11 +93,11 @@ export default (plop) => { validate: (apiPaths) => apiPaths.length > 0, }, { - type: 'checkbox', - name: 'templates', - message: 'Which templates do you want to generate?', - choices: ['listing', 'dashboard', 'onboarding'], + type: 'list', + name: 'listingEndpoint', + message: 'What is the listing endpoint?', when: async (data) => { + data.templates = ['listing', 'onboarding', 'dashboard']; data.apiPathsByApiVersion = data.apiPaths.reduce( (res, path) => { res[isV2Endpoint(path) ? 'v2' : 'v6'].push(path); @@ -113,19 +113,12 @@ export default (plop) => { ); return true; }, - }, - { - type: 'list', - name: 'listingEndpoint', - message: 'What is the listing endpoint?', - when: (data) => data.templates.includes('listing'), choices: getApiV2AndV6GetEndpointsChoices, }, { type: 'list', name: 'dashboardEndpoint', message: 'What is the dashboard endpoint?', - when: (data) => data.templates.includes('dashboard'), choices: getApiV2AndV6GetEndpointsChoices, }, { @@ -133,58 +126,40 @@ export default (plop) => { name: 'serviceKey', message: 'What is the service key ?', when: (data) => { - // Add variables for templates - data.isPCI = data.appName.indexOf('pci') > -1; if (data.isPCI) { data.pciName = data.appName.split('pci-')[1]; } - data.hasListing = data.templates.includes('listing'); - data.hasDashboard = data.templates.includes('dashboard'); - data.hasOnboarding = data.templates.includes('onboarding'); - data.isApiV6 = data.apiV6Endpoints.get?.operationList.length > 0; data.isApiV2 = data.apiV2Endpoints.get?.operationList.length > 0; - if (data.hasListing) { - const [listingPath, listingFn] = - data.listingEndpoint?.split('-') || []; - data.listingEndpointPath = listingPath; - data.listingEndpointFn = listingFn; - data.mainApiPath = listingPath; - data.mainApiPathApiVersion = data.apiV2Endpoints.get?.operationList - .map(({ apiPath }) => apiPath) - .includes(data.mainApiPath) - ? 'v2' - : 'v6'; + const [listingPath, listingFn] = + data.listingEndpoint?.split('-') || []; + data.listingEndpointPath = listingPath; + data.listingEndpointFn = listingFn; + data.mainApiPath = listingPath; + data.mainApiPathApiVersion = data.apiV2Endpoints.get?.operationList + .map(({ apiPath }) => apiPath) + .includes(data.mainApiPath) + ? 'v2' + : 'v6'; - if (data.isPCI) { - if (data.isApiV2) { - data.mainApiPathPci = listingPath.replace( - '{projectId}', - '${projectId}', - ); - } - if (data.isApiV6) { - data.mainApiPathPci = listingPath.replace( - '{serviceName}', - '${projectId}', - ); - } - } - } - if (data.hasDashboard) { - const [dashboardPath, dashboardFn] = - data.dashboardEndpoint?.split('-') || []; - data.dashboardEndpointPath = dashboardPath; - data.dashboardEndpointFn = dashboardFn; + if (data.isPCI) { + data.mainApiPathPci = listingPath.replace( + data.isApiV2 ? '{projectId}' : '{serviceName}', + '${projectId}', + ); } + const [dashboardPath, dashboardFn] = + data.dashboardEndpoint?.split('-') || []; + data.dashboardEndpointPath = dashboardPath; + data.dashboardEndpointFn = dashboardFn; const { apiV2Computed, apiV6Computed } = apiComputed(data); data.apiV2Computed = apiV2Computed; data.apiV6Computed = apiV6Computed; - return data.hasListing; + return true; }, validate: (input) => input.length > 0, }, @@ -192,14 +167,6 @@ export default (plop) => { type: 'input', name: 'serviceKey', message: 'What is the service key in listing page ?', - when: (data) => { - // Add variables for templates - data.hasListing = data.templates.includes('listing'); - data.hasDashboard = data.templates.includes('dashboard'); - data.hasOnboarding = data.templates.includes('onboarding'); - - return data.templates.includes('listing'); - }, validate: (input) => input.length > 0, }, { diff --git a/packages/manager/core/generator/app/templates/cucumber.js b/packages/manager/core/generator/app/templates/cucumber.js new file mode 100644 index 000000000000..8e6abbfbca8f --- /dev/null +++ b/packages/manager/core/generator/app/templates/cucumber.js @@ -0,0 +1,20 @@ +const isCI = process.env.CI; + +module.exports = { + default: { + paths: ['e2e/features/**/*.feature'], + require: [ + '../../../../playwright-helpers/bdd-setup.ts', + 'e2e/**/*.step.ts', + ], + requireModule: ['ts-node/register'], + format: [ + 'summary', + isCI ? 'progress' : 'progress-bar', + !isCI && ['html', 'e2e/reports/cucumber-results-report.html'], + !isCI && ['usage-json', 'e2e/reports/cucumber-usage-report.json'], + ].filter(Boolean), + formatOptions: { snippetInterface: 'async-await' }, + retry: 1, + }, +}; diff --git a/packages/manager/core/generator/app/templates/e2e/features/error.feature b/packages/manager/core/generator/app/templates/e2e/features/error.feature new file mode 100644 index 000000000000..e20e56f5f592 --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/features/error.feature @@ -0,0 +1,12 @@ +Feature: Error + + Scenario Outline: Display an error if request fails + Given The service to fetch the data is + When User navigates to Home page + Then User "" the list of data + Then User sees error + + Examples: + | apiOk | sees | anyError | + | OK | sees | no | + | KO | doesn't see | an | diff --git a/packages/manager/core/generator/app/templates/e2e/features/onboarding.feature b/packages/manager/core/generator/app/templates/e2e/features/onboarding.feature new file mode 100644 index 000000000000..fb209fc96305 --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/features/onboarding.feature @@ -0,0 +1,7 @@ +Feature: Onboarding page + + Scenario: User wants to find informations related to {{appName}} + Given User has 0 elements in the Listing page + When User navigates to Listing page + Then User gets redirected to Onboarding page + Then User sees 3 guides diff --git a/packages/manager/core/generator/app/templates/e2e/step-definitions/error.step.ts.hbs b/packages/manager/core/generator/app/templates/e2e/step-definitions/error.step.ts.hbs new file mode 100644 index 000000000000..ac3cc4786bbb --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/step-definitions/error.step.ts.hbs @@ -0,0 +1,53 @@ +import { Given, When, Then } from '@cucumber/cucumber'; +import { expect } from '@playwright/test'; +import { ICustomWorld } from '../../../../../../playwright-helpers'; +import { ConfigParams, getUrl, setupNetwork } from '../utils'; +import { title } from '../../public/translations/listing/Messages_fr_FR.json'; +import { + manager_error_page_title, + manager_error_page_action_home_label, + manager_error_page_action_reload_label, +} from '../../public/translations/{{appName}}/error/Messages_fr_FR.json'; + +Given('The service to fetch the data is {word}', function( + this: ICustomWorld, + apiState: 'OK' | 'KO', +) { + this.handlersConfig.isKo = apiState === 'KO'; +}); + +When('User navigates to Home page', async function( + this: ICustomWorld, +) { + await setupNetwork(this); + await this.page.goto(this.testContext.initialUrl || getUrl('root'), { + waitUntil: 'load', + }); +}); + +Then('User {string} the list of data', async function( + this: ICustomWorld, + see: 'sees' | "doesn't see", +) { + if (see === 'sees') { + const titleElement = await this.page.getByText(title); + await expect(titleElement).toBeVisible(); + } +}); + +Then('User sees {word} error', async function( + this: ICustomWorld, + anyError: 'an' | 'no', +) { + if (anyError === 'an') { + await expect(this.page.getByText(manager_error_page_title)).toBeVisible(); + + await expect( + this.page.getByText(manager_error_page_action_home_label), + ).toBeVisible(); + + await expect( + this.page.getByText(manager_error_page_action_reload_label), + ).toBeVisible(); + } +}); diff --git a/packages/manager/core/generator/app/templates/e2e/step-definitions/onboarding.step.ts b/packages/manager/core/generator/app/templates/e2e/step-definitions/onboarding.step.ts new file mode 100644 index 000000000000..67b20b6e52c8 --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/step-definitions/onboarding.step.ts @@ -0,0 +1,32 @@ +import { Given, When, Then } from '@cucumber/cucumber'; +import { expect } from '@playwright/test'; +import { ICustomWorld } from '../../../../../../playwright-helpers'; +import { ConfigParams, getUrl, setupNetwork } from '../utils'; + +Given('User has {int} elements in the Listing page', function( + this: ICustomWorld, + nb: number, +) { + this.handlersConfig.nb = nb; +}); + +When('User navigates to Listing page', async function( + this: ICustomWorld, +) { + await setupNetwork(this); + await this.page.goto(getUrl('listing'), { waitUntil: 'load' }); +}); + +Then('User gets redirected to Onboarding page', async function( + this: ICustomWorld, +) { + await expect(this.page).toHaveURL(getUrl('onboarding')); +}); + +Then('User sees {int} guides', async function( + this: ICustomWorld, + nbGuides: number, +) { + const guides = await this.page.locator('osds-tile'); + await expect(guides).toHaveCount(nbGuides); +}); diff --git a/packages/manager/core/generator/app/templates/e2e/utils/constants.ts b/packages/manager/core/generator/app/templates/e2e/utils/constants.ts new file mode 100644 index 000000000000..866e6dadabe9 --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/utils/constants.ts @@ -0,0 +1,7 @@ +import { urls } from '../../src/routes/routes.constant'; + +export const appUrl = 'http://localhost:9001/app'; + +export type AppRoute = keyof typeof urls; + +export const getUrl = (route: AppRoute) => `${appUrl}/#${urls[route]}`; diff --git a/packages/manager/core/generator/app/templates/e2e/utils/index.tsx b/packages/manager/core/generator/app/templates/e2e/utils/index.tsx new file mode 100644 index 000000000000..24a453c58aa9 --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/utils/index.tsx @@ -0,0 +1,2 @@ +export * from './network'; +export * from './constants'; diff --git a/packages/manager/core/generator/app/templates/e2e/utils/network.ts b/packages/manager/core/generator/app/templates/e2e/utils/network.ts new file mode 100644 index 000000000000..46d894b9a08e --- /dev/null +++ b/packages/manager/core/generator/app/templates/e2e/utils/network.ts @@ -0,0 +1,28 @@ +import { BrowserContext } from '@playwright/test'; +import { + ICustomWorld, + toPlaywrightMockHandler, + Handler, +} from '../../../../../../playwright-helpers'; +import { + GetAuthenticationMocks, + getAuthenticationMocks, +} from '../../../../../../playwright-helpers/mocks/auth'; +import { getExampleMocks, GetExampleMocksParams } from '../../mocks'; + +export type ConfigParams = GetAuthenticationMocks & GetExampleMocksParams; + +export const getConfig = (params: ConfigParams): Handler[] => + [getAuthenticationMocks, getExampleMocks].flatMap((getMocks) => + getMocks(params), + ); + +export const setupNetwork = async (world: ICustomWorld) => + Promise.all( + getConfig({ + ...((world?.handlersConfig as ConfigParams) || ({} as ConfigParams)), + isAuthMocked: true, + }) + .reverse() + .map(toPlaywrightMockHandler(world.context as BrowserContext)), + ); diff --git a/packages/manager/core/generator/app/templates/mocks/example/example-data.json b/packages/manager/core/generator/app/templates/mocks/example/example-data.json new file mode 100644 index 000000000000..16b09c4e47fd --- /dev/null +++ b/packages/manager/core/generator/app/templates/mocks/example/example-data.json @@ -0,0 +1,11 @@ +[ + { + "id": 20374 + }, + { + "id": 20375 + }, + { + "id": 20379 + } +] diff --git a/packages/manager/core/generator/app/templates/mocks/example/example.ts b/packages/manager/core/generator/app/templates/mocks/example/example.ts new file mode 100644 index 000000000000..77c033e7b392 --- /dev/null +++ b/packages/manager/core/generator/app/templates/mocks/example/example.ts @@ -0,0 +1,30 @@ +import { Handler } from '../../../../../../playwright-helpers'; +import exampleList from './example-data.json'; + +export type GetExampleMocksParams = { isKo?: boolean; nb?: number }; + +export const getExampleMocks = ({ + isKo, + nb = Number.POSITIVE_INFINITY, +}: GetExampleMocksParams): Handler[] => [ + { + url: '*', + response: isKo + ? { + message: 'Example error', + } + : exampleList.slice(0, nb), + status: isKo ? 500 : 200, + api: 'v6', + }, + { + url: '*', + response: isKo + ? { + message: 'Example error', + } + : exampleList.slice(0, nb), + status: isKo ? 500 : 200, + api: 'v2', + }, +]; diff --git a/packages/manager/core/generator/app/templates/mocks/index.ts b/packages/manager/core/generator/app/templates/mocks/index.ts new file mode 100644 index 000000000000..4356d0ac05ac --- /dev/null +++ b/packages/manager/core/generator/app/templates/mocks/index.ts @@ -0,0 +1 @@ +export * from './example/example'; diff --git a/packages/manager/core/generator/app/templates/package.json.hbs b/packages/manager/core/generator/app/templates/package.json.hbs index fc55a814b643..d3134cada94a 100644 --- a/packages/manager/core/generator/app/templates/package.json.hbs +++ b/packages/manager/core/generator/app/templates/package.json.hbs @@ -16,9 +16,8 @@ "start": "lerna exec --stream --scope='{{ packageName }}' --include-dependencies -- npm run build --if-present", "start:dev": "lerna exec --stream --scope='{{ packageName }}' --include-dependencies -- npm run dev --if-present", "start:watch": "lerna exec --stream --parallel --scope='{{ packageName }}' --include-dependencies -- npm run dev:watch --if-present", - "test:e2e": "tsc && npx playwright test --headed", - "test:e2e:cii": "tsc && npx playwright test", - "test:e2e:script": "tsc && node ../../../../scripts/run-playwright.js" + "test:e2e": "tsc && node ../../../../scripts/run-playwright-bdd.js", + "test:e2e:ci": "tsc && node ../../../../scripts/run-playwright-bdd.js --ci" }, "dependencies": { "@ovh-ux/manager-config": "*", @@ -45,9 +44,10 @@ "tailwindcss": "^3.3.3" }, "devDependencies": { + "@cucumber/cucumber": "^10.3.1", "@ovh-ux/manager-vite-config": "*", "typescript": "^4.3.2", - "@playwright/test": "^1.34.3", + "@playwright/test": "^1.41.2", "vite": "^4.5.0" }, "regions": [ diff --git a/packages/manager/core/generator/app/templates/playwright.config.ts b/packages/manager/core/generator/app/templates/playwright.config.ts index 4a033cc8d87d..feb249bcbe3f 100644 --- a/packages/manager/core/generator/app/templates/playwright.config.ts +++ b/packages/manager/core/generator/app/templates/playwright.config.ts @@ -12,4 +12,9 @@ export default defineConfig({ // Collect trace when retrying the failed test. trace: 'retain-on-failure', }, + testMatch: '**/*.e2e.ts', + webServer: { + command: 'yarn run dev', + url: 'http://localhost:9000/', + }, }); diff --git a/packages/manager/core/generator/app/templates/src/routes/routes.tsx.hbs b/packages/manager/core/generator/app/templates/src/routes/routes.tsx.hbs index 07ae1f5ebd4a..f0fc7e31ae2a 100644 --- a/packages/manager/core/generator/app/templates/src/routes/routes.tsx.hbs +++ b/packages/manager/core/generator/app/templates/src/routes/routes.tsx.hbs @@ -104,7 +104,7 @@ export const Routes: any = [ id: 'dashboard', path: '', ...lazyRouteConfig( - () => import('@/pages/dashboard/index')), + () => import('@/pages/dashboard/general-informations')), handle: { tracking: { pageName: 'dashboard', @@ -116,7 +116,7 @@ export const Routes: any = [ id: 'dashboard.tab2', path: 'Tab2', ...lazyRouteConfig( - () => import('@/pages/dashboard/Tab2')), + () => import('@/pages/dashboard/tab2')), handle: { tracking: { pageName: 'tab2', diff --git a/packages/manager/core/generator/app/templates/src/{{appName}}.config.ts.hbs b/packages/manager/core/generator/app/templates/src/{{appName}}.config.ts.hbs index f98a5eba1870..4ff7a46a611d 100644 --- a/packages/manager/core/generator/app/templates/src/{{appName}}.config.ts.hbs +++ b/packages/manager/core/generator/app/templates/src/{{appName}}.config.ts.hbs @@ -1,10 +1,8 @@ export default { - {{#if this.hasListing}} listing: { datagrid: { serviceKey: '{{this.serviceKey}}', }, }, - {{/if}} rootLabel: '{{appName}}', }; diff --git a/packages/manager/core/generator/app/templates/tsconfig.json b/packages/manager/core/generator/app/templates/tsconfig.json index 9895f8a68b4c..e2104f471575 100644 --- a/packages/manager/core/generator/app/templates/tsconfig.json +++ b/packages/manager/core/generator/app/templates/tsconfig.json @@ -19,8 +19,7 @@ "jsx": "react", "baseUrl": ".", "paths": { - "@/*": ["./src/*"], - "@playwright-helpers/*": ["../../../../playwright-helpers/*"] + "@/*": ["./src/*"] } }, "include": ["src"], diff --git a/packages/manager/core/generator/app/templates/tsconfig.test.json b/packages/manager/core/generator/app/templates/tsconfig.test.json new file mode 100644 index 000000000000..7048c297c8f6 --- /dev/null +++ b/packages/manager/core/generator/app/templates/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS" + } +} diff --git a/packages/manager/core/generator/app/universes.constant.js b/packages/manager/core/generator/app/universes.constant.js index 52deba0e6581..2b1170378d25 100644 --- a/packages/manager/core/generator/app/universes.constant.js +++ b/packages/manager/core/generator/app/universes.constant.js @@ -31,7 +31,7 @@ export const SUB_UNIVERSES = [ 'Account-creation', ]; export const LEVEL2 = { - 0: '', + 0: 'Unknown', 56: 'ManagerCloud', 57: 'ManagerDedicated', 67: 'Focus', diff --git a/scripts/run-playwright-bdd.js b/scripts/run-playwright-bdd.js index 6842618101c0..5e823ccda001 100644 --- a/scripts/run-playwright-bdd.js +++ b/scripts/run-playwright-bdd.js @@ -13,21 +13,24 @@ console.log( const runTests = async () => { let exitCode = 0; - const getBaseConfig = await import( - '../packages/manager/core/vite-config/src/index.js' - ).then((module) => module.getBaseConfig); + let server; - const server = await createServer({ - ...getBaseConfig(), - server: { - port: 9001, - }, - envDir: __dirname, - }); - - await server.listen(); + const baseConfig = await import(`${process.cwd()}/vite.config.mjs`).then( + (module) => module.default, + ); try { + server = await createServer({ + ...baseConfig, + plugins: [], + server: { + port: 9001, + }, + envDir: __dirname, + }); + + await server.listen(); + const result = await execa('npx', ['cucumber-js'], { stdio: 'inherit', detached: true, @@ -44,7 +47,7 @@ const runTests = async () => { console.log('error:', err); exitCode = 2; } finally { - await server.close(); + await server?.close(); } return exitCode; };