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..7f4ec00a5 100644 --- a/projects/movies/project.json +++ b/projects/movies/project.json @@ -229,6 +229,8 @@ "development": { "verbose": true, "dryRun": true, + "openReport": false, + "ufPath": "projects/movies/user-flows", "serveCommand": "nx run movies:serve:development", "awaitServeStdout": "Angular Live Development Server is listening on" }, 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..bab7f626b --- /dev/null +++ b/projects/movies/user-flows/movie-details.uf.ts @@ -0,0 +1,71 @@ +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"; + +const flowOptions: UserFlowOptions = { + name: 'Movie Detail Tests', +}; + +const interactions: UserFlowInteractionsFn = async ( + ctx: UserFlowContext +): Promise => { + const {flow, collectOptions} = ctx; + const url = `${collectOptions.url}/list/category/popular`; + const movieListPage = new MovieListPageUFO(ctx); + const movieDetailPage = new MovieDetailPageUFO(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!") + } + await flow.endTimespan(); + await flow.snapshot({ + stepName: '✔ Navigation to other detail page done', + }); + + return Promise.resolve(); +}; + +export const userFlowProvider: UserFlowProvider = { + flowOptions, + interactions, +}; + +module.exports = userFlowProvider; diff --git a/projects/movies/user-flows/person-details.md b/projects/movies/user-flows/person-details.md new file mode 100644 index 000000000..e56d87b60 --- /dev/null +++ b/projects/movies/user-flows/person-details.md @@ -0,0 +1,89 @@ +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 person detail', +}); + +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', +}); + +// ======= Hero image change for cast ======= + +await flow.startTimespan({ +stepName: '🧭 Navigate to movie detail', +}); +try { +await personDetailPage.goToMovieDetail(1); +await movieDetailPage.awaitAllContent(); + + const castImageSrc = await movieDetailPage.getHeroImageSrc(); + if (castImageSrc === currentHeroImageSrc) { + throw new Error("Hero image does not change when navigating to cast!") + } + +} catch (e) { + + return Promise.resolve(); + +} + +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;