Как грамотно реализовать общие компоненты у страниц

Всем привет! Важная ремарка - это моя первая тема, а также я только начинаю погружаться в автоматизацию, с нуля внедряю в проект, где ее никогда не было, так что опыта у меня мало.

Столкнулся с проблемой циклических импортов в своей архитектуре. Пример кода будет с сайта https://www.saucedemo.com/, чтобы многие были в контексте.

Вводные:

  1. В случае с упомянутым сайтом у нас есть несколько страниц: LoginPage, InventoryPage, CartPage и тд.
  2. Также есть компоненты, которые хочется сделать в виде отдельных классов: BurgerMenu, Header

Варианты реализации, которые вижу я:

  1. Вариант, который чаще всего вижу в интернетах: С помощью композиции объявить Header в InventoryPage, CartPage и тд
  2. Написать публичные методы, которые будут обращаться к этим компонентам

Пример такой страницы:

// inventory-page.ts
import { MainHeader } from "../../common/components/main-header";
import { BasePage } from "../../common/pages/base-page";
import { CartPage } from "../../cart/pages/cart-page";


export class InventoryPage extends BasePage {
  protected readonly url = '/inventory.html';

  private readonly header = new MainHeader(this.page);

  public async openCartPage(): Promise<CartPage> {
    await this.header.clickOnCart();
    
    return new CartPage(this.page);
  }
}

Пример части кода теста

const cartPage = await inventoryPage.openCartPage(); 

Вроде бы неплохо, но что лично сразу смущает меня - написание отдельного метода на уровне страницы, у общего для нескольких страниц компонента. Сразу пришло в голову сделать header публичным и не писать лишний метод:

// inventory-page.ts
import { MainHeader } from "../../common/components/main-header";
import { BasePage } from "../../common/pages/base-page";


export class InventoryPage extends BasePage {
  protected readonly url = '/inventory.html';

  public readonly header = new MainHeader(this.page);
}

Пример части кода теста

const cartPage = await inventoryPage.header.clickOnCart(); 

Лично такой вариант мне нравится больше, т.к. теперь я могу использовать clickOnCart у любой страницы, у которой есть header .

Дальше я решил указать Header в BasePage

// base-page.ts
import test from "@playwright/test";
import { BasePageObject } from "../base-page-object";
import { MainHeader } from "../components/main-header";

export abstract class BasePage extends BasePageObject {
  protected abstract url: string;

  public readonly header = new MainHeader(this.page);

  public async open(): Promise<void> {
    await test.step(`Go to url "${this.baseUrl}${this.url}"`, async () => {
      await this.page.goto(this.url);      
    });
  }

  private get baseUrl(): string {
    return test.info().project.use.baseURL as string;
  }
}

Теперь метод clickOnCart будет у всех страниц, которые наследуются от BasePage.

НО! Теперь я сталкиваюсь с циклическими импортами, т.к.

  • BasePage зависит от MainHeader
  • MainHeader зависит от CartPage, потому что clickOnCart возращает его экземпляр
  • CartPage зависит от BasePage
  • BasePage зависит от MainHeader

Вот и вопрос: как вы продумываете работу с компонентами? В случае с простым приложением понятно - прокинул везде вручную и забыл, но если таких общих компонентов много, то как быть?