From b3a5fb685d19d340009bb25e60caebad85b13214 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 17 Jul 2023 16:47:07 +0200 Subject: [PATCH 1/5] test: add test for hero image switch for detail mivie and person --- .../src/ufo/desktop/movie-detail-page.ufo.ts | 5 + .../src/ufo/desktop/movie-list-page.ufo.ts | 4 + .../src/ufo/desktop/person-detail-page.ufo.ts | 51 ++++++++++ projects/movies/project.json | 5 +- .../movies/user-flows/movie-details.uf.ts | 95 +++++++++++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 projects/movies-user-flows/src/ufo/desktop/person-detail-page.ufo.ts create mode 100644 projects/movies/user-flows/movie-details.uf.ts diff --git a/projects/movies-user-flows/src/ufo/desktop/movie-detail-page.ufo.ts b/projects/movies-user-flows/src/ufo/desktop/movie-detail-page.ufo.ts index 2e58e4a47..e351ab262 100644 --- a/projects/movies-user-flows/src/ufo/desktop/movie-detail-page.ufo.ts +++ b/projects/movies-user-flows/src/ufo/desktop/movie-detail-page.ufo.ts @@ -28,6 +28,11 @@ export class MovieDetailPageUFO await this.page.waitForSelector(heroImageSelector); } + async getHeroImageSrc() { + await this.page.waitForSelector(heroImageSelector); + return await this.page.$$eval(heroImageSelector, async imgs => await imgs.map(img => img.getAttribute('src')).pop()); + } + async awaitHeadingContent(): Promise { await Promise.all([ this.page.waitForSelector(heandlineSelector), diff --git a/projects/movies-user-flows/src/ufo/desktop/movie-list-page.ufo.ts b/projects/movies-user-flows/src/ufo/desktop/movie-list-page.ufo.ts index 3753b7f9e..cab65f2f2 100644 --- a/projects/movies-user-flows/src/ufo/desktop/movie-list-page.ufo.ts +++ b/projects/movies-user-flows/src/ufo/desktop/movie-list-page.ufo.ts @@ -21,6 +21,10 @@ export class MovieListPageUFO extends Ufo implements CwvInterface { ]); } + async awaitHeroImg() { + await this.movieList; + } + async awaitAllContent() { await Promise.all([ this.awaitHeadingContent(), diff --git a/projects/movies-user-flows/src/ufo/desktop/person-detail-page.ufo.ts b/projects/movies-user-flows/src/ufo/desktop/person-detail-page.ufo.ts new file mode 100644 index 000000000..402ee7e1a --- /dev/null +++ b/projects/movies-user-flows/src/ufo/desktop/person-detail-page.ufo.ts @@ -0,0 +1,51 @@ +import {UiMovieListUFO} from './ui-movie-list.ufo'; +import {UiCastListUFO} from './ui-cast-list.ufo'; +import {CwvInterface} from '../typings/cwv.interface'; +import {BackNavigationInterface} from '../typings/back-navigation.interface'; +import {backBtnSelector, heandlineSelector, heroImageSelector, subheandlineSelector} from '../../../../movies/testing'; +import {Ufo, UserFlowContext} from '@push-based/user-flow'; + +export class PersonDetailPageUFO + extends Ufo + implements CwvInterface, BackNavigationInterface { + castList = new UiCastListUFO(this.ctx); + movieList = new UiMovieListUFO(this.ctx); + + async navigateBack(): Promise { + await this.page.waitForSelector(backBtnSelector); + await this.page.click(backBtnSelector); + } + + async goToMovieDetail(id: number): Promise { + await this.movieList.clickMovieListImage(id); + } + + async awaitLCPContent(): Promise { + await this.page.waitForSelector(heroImageSelector); + } + + async getHeroImageSrc() { + await this.page.waitForSelector(heroImageSelector); + return await this.page.$$eval(heroImageSelector, async imgs => await imgs.map(img => img.getAttribute('src')).pop()); + } + + async awaitHeadingContent(): Promise { + await Promise.all([ + this.page.waitForSelector(heandlineSelector), + this.page.waitForSelector(subheandlineSelector), + ]); + } + + async awaitAllContent(): Promise { + await Promise.all([ + this.awaitLCPContent(), + this.awaitHeadingContent(), + this.movieList.awaitAllContent(), + this.castList.awaitAllContent(), + ]); + } + + constructor(private ctx: UserFlowContext) { + super(ctx); + } +} diff --git a/projects/movies/project.json b/projects/movies/project.json index c1b91c02e..cb9ee06c5 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -229,7 +229,10 @@ "development": { "verbose": true, "dryRun": true, - "serveCommand": "nx run movies:serve:development", + "openReport": false, + "ufPath": "projects/movies/user-flows/movie-details.uf.ts", + "url": "http://localhost:4207", + "serveCommand": "nx run movies:serve:development --port=4207", "awaitServeStdout": "Angular Live Development Server is listening on" }, "production": { diff --git a/projects/movies/user-flows/movie-details.uf.ts b/projects/movies/user-flows/movie-details.uf.ts new file mode 100644 index 000000000..76bb9132a --- /dev/null +++ b/projects/movies/user-flows/movie-details.uf.ts @@ -0,0 +1,95 @@ +import {UserFlowContext, UserFlowInteractionsFn, UserFlowOptions, UserFlowProvider,} from '@push-based/user-flow'; + +import {mergeBudgets, MovieDetailPageUFO, MovieListPageUFO,} from '../../movies-user-flows/src'; +import {getLhConfig} from "../../movies-user-flows/src/internals/test-sets"; +import * as angularBudgets from "../testing/budgets/angular.budgets.json"; +import * as generalTimingBudget from "../testing/budgets/general-timing.budgets.json"; +import * as movieListBudgets from "../testing/budgets/movie-list.budgets.json"; +import {PersonDetailPageUFO} from "../../movies-user-flows/src/ufo/desktop/person-detail-page.ufo"; + +const flowOptions: UserFlowOptions = { + name: 'Basic user flow to ensure basic functionality', +}; + +const interactions: UserFlowInteractionsFn = async ( + ctx: UserFlowContext +): Promise => { + const {flow, collectOptions, page} = ctx; + const url = `${collectOptions.url}/list/category/popular`; + const movieListPage = new MovieListPageUFO(ctx); + const movieDetailPage = new MovieDetailPageUFO(ctx); + const personDetailPage = new PersonDetailPageUFO(ctx); + + await flow.navigate(url, { + stepName: '🧭 Initial navigation', + config: getLhConfig( + mergeBudgets([angularBudgets, generalTimingBudget, movieListBudgets]) + ) + }); + await flow.snapshot({ + stepName: '✔ Initial navigation done', + }); + + + let currentHeroImageSrc = undefined; + + // ======= Detail navigations ======= + await flow.startTimespan({ + stepName: '🧭 Navigate to detail page', + }); + await movieListPage.navigateToDetail(1); + await movieDetailPage.awaitAllContent(); + currentHeroImageSrc = await movieDetailPage.getHeroImageSrc(); + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to detail page done', + }); + + // ======= Hero image change for recommendations ======= + + await flow.startTimespan({ + stepName: '🧭 Navigate to other detail page', + }); + await movieListPage.navigateToDetail(1); + await movieDetailPage.awaitAllContent(); + + const recommendationImageSrc = await movieDetailPage.getHeroImageSrc(); + if (recommendationImageSrc === currentHeroImageSrc) { + throw new Error("hero image does not change when navigating to recommended movie!") + } else { + currentHeroImageSrc = recommendationImageSrc + } + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to other detail page done', + }); + + + // ======= Hero image change for cast ======= + await flow.startTimespan({ + stepName: '🧭 Navigate to cast', + }); + await movieDetailPage.goToPersonDetail(1); + await personDetailPage.awaitAllContent(); + + const castImageSrc = await personDetailPage.getHeroImageSrc(); + if (castImageSrc === currentHeroImageSrc) { + throw new Error("hero image does not change when navigating to cast!") + } else { + currentHeroImageSrc = castImageSrc + } + + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to cast detail page done', + }); + + return Promise.resolve(); +}; + +export const userFlowProvider: UserFlowProvider = { + flowOptions, + interactions, +}; + +module.exports = userFlowProvider; From 76a8164752bebe82d1086611f7f9216ed719fa14 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 17 Jul 2023 16:47:35 +0200 Subject: [PATCH 2/5] refactor: cleanup --- projects/movies/project.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/movies/project.json b/projects/movies/project.json index cb9ee06c5..7f4ec00a5 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -230,9 +230,8 @@ "verbose": true, "dryRun": true, "openReport": false, - "ufPath": "projects/movies/user-flows/movie-details.uf.ts", - "url": "http://localhost:4207", - "serveCommand": "nx run movies:serve:development --port=4207", + "ufPath": "projects/movies/user-flows", + "serveCommand": "nx run movies:serve:development", "awaitServeStdout": "Angular Live Development Server is listening on" }, "production": { From 4c49dff6cc8e69f987c817c177f2860c98a46d08 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 17 Jul 2023 16:52:42 +0200 Subject: [PATCH 3/5] refactor: cleanup name --- projects/movies/user-flows/movie-details.uf.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/movies/user-flows/movie-details.uf.ts b/projects/movies/user-flows/movie-details.uf.ts index 76bb9132a..0604b2147 100644 --- a/projects/movies/user-flows/movie-details.uf.ts +++ b/projects/movies/user-flows/movie-details.uf.ts @@ -8,7 +8,7 @@ import * as movieListBudgets from "../testing/budgets/movie-list.budgets.json"; import {PersonDetailPageUFO} from "../../movies-user-flows/src/ufo/desktop/person-detail-page.ufo"; const flowOptions: UserFlowOptions = { - name: 'Basic user flow to ensure basic functionality', + name: 'Movie Detail and Person Detail Tests', }; const interactions: UserFlowInteractionsFn = async ( From 65ff4229fdbf60ba4cee5e687da2c944046b3951 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 17 Jul 2023 18:36:18 +0200 Subject: [PATCH 4/5] refactor: add person detail page --- .../movies/user-flows/movie-details.uf.ts | 28 +------ .../movies/user-flows/person-details.uf.ts | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 projects/movies/user-flows/person-details.uf.ts diff --git a/projects/movies/user-flows/movie-details.uf.ts b/projects/movies/user-flows/movie-details.uf.ts index 0604b2147..bab7f626b 100644 --- a/projects/movies/user-flows/movie-details.uf.ts +++ b/projects/movies/user-flows/movie-details.uf.ts @@ -5,20 +5,18 @@ import {getLhConfig} from "../../movies-user-flows/src/internals/test-sets"; import * as angularBudgets from "../testing/budgets/angular.budgets.json"; import * as generalTimingBudget from "../testing/budgets/general-timing.budgets.json"; import * as movieListBudgets from "../testing/budgets/movie-list.budgets.json"; -import {PersonDetailPageUFO} from "../../movies-user-flows/src/ufo/desktop/person-detail-page.ufo"; const flowOptions: UserFlowOptions = { - name: 'Movie Detail and Person Detail Tests', + name: 'Movie Detail Tests', }; const interactions: UserFlowInteractionsFn = async ( ctx: UserFlowContext ): Promise => { - const {flow, collectOptions, page} = ctx; + const {flow, collectOptions} = ctx; const url = `${collectOptions.url}/list/category/popular`; const movieListPage = new MovieListPageUFO(ctx); const movieDetailPage = new MovieDetailPageUFO(ctx); - const personDetailPage = new PersonDetailPageUFO(ctx); await flow.navigate(url, { stepName: '🧭 Initial navigation', @@ -56,34 +54,12 @@ const interactions: UserFlowInteractionsFn = async ( const recommendationImageSrc = await movieDetailPage.getHeroImageSrc(); if (recommendationImageSrc === currentHeroImageSrc) { throw new Error("hero image does not change when navigating to recommended movie!") - } else { - currentHeroImageSrc = recommendationImageSrc } await flow.endTimespan(); await flow.snapshot({ stepName: '✔ Navigation to other detail page done', }); - - // ======= Hero image change for cast ======= - await flow.startTimespan({ - stepName: '🧭 Navigate to cast', - }); - await movieDetailPage.goToPersonDetail(1); - await personDetailPage.awaitAllContent(); - - const castImageSrc = await personDetailPage.getHeroImageSrc(); - if (castImageSrc === currentHeroImageSrc) { - throw new Error("hero image does not change when navigating to cast!") - } else { - currentHeroImageSrc = castImageSrc - } - - await flow.endTimespan(); - await flow.snapshot({ - stepName: '✔ Navigation to cast detail page done', - }); - return Promise.resolve(); }; diff --git a/projects/movies/user-flows/person-details.uf.ts b/projects/movies/user-flows/person-details.uf.ts new file mode 100644 index 000000000..12586bdbc --- /dev/null +++ b/projects/movies/user-flows/person-details.uf.ts @@ -0,0 +1,77 @@ +import {UserFlowContext, UserFlowInteractionsFn, UserFlowOptions, UserFlowProvider,} from '@push-based/user-flow'; + +import {mergeBudgets, MovieDetailPageUFO, MovieListPageUFO,} from '../../movies-user-flows/src'; +import {getLhConfig} from "../../movies-user-flows/src/internals/test-sets"; +import * as angularBudgets from "../testing/budgets/angular.budgets.json"; +import * as generalTimingBudget from "../testing/budgets/general-timing.budgets.json"; +import * as movieListBudgets from "../testing/budgets/movie-list.budgets.json"; +import {PersonDetailPageUFO} from "../../movies-user-flows/src/ufo/desktop/person-detail-page.ufo"; + +const flowOptions: UserFlowOptions = { + name: 'Person Detail Tests', +}; + +const interactions: UserFlowInteractionsFn = async ( + ctx: UserFlowContext +): Promise => { + const {flow, collectOptions, page} = ctx; + const url = `${collectOptions.url}/list/category/popular`; + const movieListPage = new MovieListPageUFO(ctx); + const movieDetailPage = new MovieDetailPageUFO(ctx); + const personDetailPage = new PersonDetailPageUFO(ctx); + + await flow.navigate(url, { + stepName: '🧭 Initial navigation', + config: getLhConfig( + mergeBudgets([angularBudgets, generalTimingBudget, movieListBudgets]) + ) + }); + await flow.snapshot({ + stepName: '✔ Initial navigation done', + }); + + + let currentHeroImageSrc = undefined; + + // ======= Detail navigations ======= + await flow.startTimespan({ + stepName: '🧭 Navigate to detail person', + }); + await movieListPage.navigateToDetail(1); + await movieDetailPage.awaitAllContent(); + await movieDetailPage.goToPersonDetail(1); + await personDetailPage.awaitAllContent(); + + currentHeroImageSrc = await personDetailPage.getHeroImageSrc(); + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to detail page done', + }); + + // ======= Hero image change for cast ======= + + await flow.startTimespan({ + stepName: '🧭 Navigate to cast', + }); + await movieDetailPage.goToPersonDetail(1); + await personDetailPage.awaitAllContent(); + + const castImageSrc = await personDetailPage.getHeroImageSrc(); + if (castImageSrc === currentHeroImageSrc) { + throw new Error("Hero image does not change when navigating to cast!") + } + + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to cast detail page done', + }); + + return Promise.resolve(); +}; + +export const userFlowProvider: UserFlowProvider = { + flowOptions, + interactions, +}; + +module.exports = userFlowProvider; From 6360193092b1f091136f64f3109e9e765ce42d9c Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 17 Jul 2023 18:58:13 +0200 Subject: [PATCH 5/5] refactor: remove person test from flow --- ...person-details.uf.ts => person-details.md} | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) rename projects/movies/user-flows/{person-details.uf.ts => person-details.md} (54%) diff --git a/projects/movies/user-flows/person-details.uf.ts b/projects/movies/user-flows/person-details.md similarity index 54% rename from projects/movies/user-flows/person-details.uf.ts rename to projects/movies/user-flows/person-details.md index 12586bdbc..e56d87b60 100644 --- a/projects/movies/user-flows/person-details.uf.ts +++ b/projects/movies/user-flows/person-details.md @@ -27,46 +27,58 @@ const interactions: UserFlowInteractionsFn = async ( ) }); await flow.snapshot({ - stepName: '✔ Initial navigation done', + stepName: '✔ Initial navigation done', }); +let currentHeroImageSrc = undefined; - let currentHeroImageSrc = undefined; +// ======= Detail navigations ======= +await flow.startTimespan({ +stepName: '🧭 Navigate to person detail', +}); - // ======= Detail navigations ======= - await flow.startTimespan({ - stepName: '🧭 Navigate to detail person', - }); - await movieListPage.navigateToDetail(1); - await movieDetailPage.awaitAllContent(); - await movieDetailPage.goToPersonDetail(1); - await personDetailPage.awaitAllContent(); +try { +await movieListPage.navigateToDetail(1); +await movieDetailPage.awaitAllContent(); +await movieDetailPage.goToPersonDetail(1); +await personDetailPage.awaitAllContent(); +} catch (e) { +await flow.endTimespan(); +return Promise.resolve(); +} - currentHeroImageSrc = await personDetailPage.getHeroImageSrc(); - await flow.endTimespan(); - await flow.snapshot({ - stepName: '✔ Navigation to detail page done', - }); +currentHeroImageSrc = await personDetailPage.getHeroImageSrc(); +await flow.endTimespan(); +await flow.snapshot({ +stepName: '✔ Navigation to detail page done', +}); - // ======= Hero image change for cast ======= +// ======= Hero image change for cast ======= - await flow.startTimespan({ - stepName: '🧭 Navigate to cast', - }); - await movieDetailPage.goToPersonDetail(1); - await personDetailPage.awaitAllContent(); +await flow.startTimespan({ +stepName: '🧭 Navigate to movie detail', +}); +try { +await personDetailPage.goToMovieDetail(1); +await movieDetailPage.awaitAllContent(); - const castImageSrc = await personDetailPage.getHeroImageSrc(); - if (castImageSrc === currentHeroImageSrc) { - throw new Error("Hero image does not change when navigating to cast!") - } + const castImageSrc = await movieDetailPage.getHeroImageSrc(); + if (castImageSrc === currentHeroImageSrc) { + throw new Error("Hero image does not change when navigating to cast!") + } - await flow.endTimespan(); - await flow.snapshot({ - stepName: '✔ Navigation to cast detail page done', - }); +} catch (e) { + + return Promise.resolve(); + +} + +await flow.endTimespan(); +await flow.snapshot({ +stepName: '✔ Navigation to cast detail page done', +}); - return Promise.resolve(); +return Promise.resolve(); }; export const userFlowProvider: UserFlowProvider = {