docs(emulation): user environment emulation guide (ru) - #139
Conversation
✅ Successfully deployed static |
|
|
||
| Пользовательская среда влияет на то, как приложение выглядит и работает. Один и тот же интерфейс может по-разному вести себя на мобильном экране, при другой локали, в тёмной теме, без доступа к геолокации или при медленном соединении. | ||
|
|
||
| В Testplane эти условия настраиваются несколькими способами: через WebDriver BiDi, обычные WebDriver-команды, CDP и capabilities браузера. Выбор механизма зависит от того, какое свойство среды нужно изменить и в каком браузере выполняется тест. |
There was a problem hiding this comment.
здесь речь идет о 4 способах эмуляции, а ниже таблица уже с тремя способами.
я бы здесь капабилити просто вынес на уровень выше как общий способ задать донастройку браузеру и добавил, что какие-то части эмулации (например, user agent) лучше выносить в настройку браузера, чтобы не применять ее к каждому тесту. Собственно, в текущей документации сейчас вообще ничего про это нет, только упоминание про capabilities
|
|
||
| <Admonition type="caution" title="Сохраняйте функцию отката"> | ||
|
|
||
| `browser.restore()` возвращает user agent, но не viewport, установленный через `emulate("device")`. Для полного отката вызывайте функцию, которую вернул `emulate("device")`. |
There was a problem hiding this comment.
про функцию restoreDevice я бы написал сразу в примере, т.к. сразу не понятно что вообще мы сохранили в переменную. и только в самом конце мы получаем эту информацию
There was a problem hiding this comment.
При этом, у нас внезапно появляется уже второй раз метод restore(), но мы все еще не знаем что он делает в подробностях
|
|
||
| `browser.restore()` возвращает user agent, но не viewport, установленный через `emulate("device")`. Для полного отката вызывайте функцию, которую вернул `emulate("device")`. | ||
|
|
||
| Viewport при этом вернётся к профилю `Desktop Chrome`, а не к исходному размеру. |
There was a problem hiding this comment.
так а делать то что с этим? :)
если мы пишем проблему, то сразу нужно приводить и пример ее решения. Например, заранее сохранить размеры окна, чтобы их потом применить
| }); | ||
| ``` | ||
|
|
||
| Команда применяется к текущему контексту и не возвращает функцию отката. Чтобы восстановить viewport, вызовите `setViewport()` повторно с нужными значениями. |
There was a problem hiding this comment.
здесь не хватает полного примера с сохранением и восстановлением размеров вьюпорта
|
|
||
| ### User agent | ||
|
|
||
| Если клиентский код выбирает мобильный интерфейс по `navigator.userAgent`, задайте значение отдельно: |
|
|
||
| Значение `1` соответствует обычной скорости, `2` замедляет CPU вдвое, `4` — вчетверо. | ||
|
|
||
| Команда поддерживается только в Chromium-браузерах с доступным CDP-подключением. В Firefox она завершается ошибкой до применения ограничений. |
There was a problem hiding this comment.
достаточно только того, что оно работает только с chromium. Думаю, и так понятно, что в остальных браузерах оно не сработает
|
|
||
| Предварительно выдавать разрешение не нужно. | ||
|
|
||
| Чтобы проверить обработку ошибки, передайте объект `Error`: |
There was a problem hiding this comment.
здесь нужно явно указать какую именно ошибку мы хотим проверить
|
|
||
| </Admonition> | ||
|
|
||
| Способ предназначен для Chromium-браузеров, поскольку использует `goog:chromeOptions`. |
There was a problem hiding this comment.
это вообще никак не связано. просто для каждого типа браузеров свое обозначение. Для chromium это goog:chromeOptions, для остальных браузеров она другая. например,
"moz:firefoxOptions": {
prefs: {
"javascript.enabled": false,
},
}
|
|
||
| Повторяющиеся сценарии удобно оформлять как отдельные браузерные профили или вспомогательные функции: например, `mobile`, `dark-theme` и `slow-network`. | ||
|
|
||
| Настройки, специфичные для одного сценария, оставляйте явными в самом тесте. Состояние `emulate()` может перейти в следующий тест даже при `isolation: true`, поэтому восстанавливайте его в том же тесте: |
There was a problem hiding this comment.
Состояние
emulate()может перейти в следующий тест даже приisolation: true
хм... звучит сомнительно. есть примеры?
There was a problem hiding this comment.
Да, есть живой пример. На стенде, который я написала для проверки утверждений из статьи, emulate() протекал в следующий it даже при isolation: true. В следующем тесте sessionId оставался тем же, но объект browser был уже другим, при этом эмулированное состояние продолжало действовать. Я отдельно проверила cleanup через afterEach, он снимает состояние корректно. Поэтому формулировку в статье уточню и добавлю пример cleanup.
| Для гарантированно чистой сессии в каждом тесте задайте для браузера: | ||
|
|
||
| ```typescript | ||
| testsPerSession: 1, |
There was a problem hiding this comment.
это очень плохой совет, т.к. на создание новой сессии может уходить много времени
может даже быть кейс, когда создание сессию будет занимать больше времени, чем время самого теста. Поэтому такую настройку нужно использовать только в крайних случаях
| let restoreDevice: (() => Promise<void>) | undefined; | ||
| let viewport: { width: number; height: number; devicePixelRatio: number }; | ||
|
|
||
| before(async ({ browser }) => { |
There was a problem hiding this comment.
а как ты проверяла этот пример? в testplane нет хука before, есть только beforeEach.
Глобальный before можно указать только в самом конфиге. Если указать такое в тесте, то твой запуск упадет еще на этапе парсинга теста с ошибкой Error: "before" and "after" hooks are forbidden, use "beforeEach" and "afterEach" hooks instead
| it("показывает инструкцию для iOS на профиле iPhone 15", async ({ browser }) => { | ||
| await browser.url("/"); | ||
|
|
||
| await expect(browser.$("[data-testid='ios-install-guide']")).toBeDisplayed(); |
There was a problem hiding this comment.
я бы везде в тестах использовал browser.getByTestId('ios-install-guide') — читается проще
|
|
||
| [`emulate("device")`][emulate-device] применяет готовый профиль устройства: viewport, DPR и `navigator.userAgent`. | ||
|
|
||
| Например, несколько тестов для iPhone можно объединить одним профилем: |
There was a problem hiding this comment.
ты пишешь о примере с несколькими тестами, но ниже приведен пример с одним тестом
| restoreDevice = await browser.emulate("device", "iPhone 15"); | ||
| }); | ||
|
|
||
| afterEach(async ({ browser }) => { |
There was a problem hiding this comment.
кажется, что для представления о работе профиля получился слишком сложный пример. Если мы сейчас просто знакомим с апи, то давай сначала только его и покажем. А потом уже в отдельном примере расскажем, что эмуляция распространяется на всю сессию и ее нужно не забывать вернуть после теста. Сейчас у тебя эти 2 сценария объединены в один
| }); | ||
| ``` | ||
|
|
||
| Функция, которую возвращает `emulate("device")`, снимает подмененный user agent, но исходный viewport не возвращает: вместо него ставится профиль Desktop Chrome — 1280 × 720, DPR 1. Поэтому в примере размер запоминается в `before` и после каждого теста возвращается через [`setViewport()`][set-viewport]. Замерять нужно на странице приложения: на `about:blank` значения будут другими. Сам `emulate("device")` по-прежнему вызывается до навигации. |
There was a problem hiding this comment.
здесь ты правильно пишешь о том, что нужно вернуть состояние браузера. Но ниже (например, для ua) такой рекомендации нет
There was a problem hiding this comment.
я бы еще добавил, что эмуляция iPhone 15 — это не точная копия интерфейса мобильного устройства. Т.е. у пользователя могут возникнуть иллюзии о том, что он сейчас и мобильное устройство протестирует без реального девайса, но это не так.
|
|
||
| ### Порядок вызовов | ||
|
|
||
| Команда `emulate()` работает через preload-скрипты BiDi: браузер применяет их при создании документа. Поэтому вызывать ее нужно до навигации: на уже открытой странице она ничего не изменит. Команда `restore()` снимает эмуляцию только для следующих документов: текущая страница останется как была, пока ее не открыть заново. |
There was a problem hiding this comment.
Поэтому вызывать ее нужно до навигации: на уже открытой странице она ничего не изменит
это не совсем так. Например, если я вызову emulate("clock"), то изменения применятся на этой же странице без перезагрузки страницы. Т.е. поведение разное в зависимости от указанных аргументов
| await browser.emulate("colorScheme", "dark"); | ||
| await browser.url("/"); | ||
|
|
||
| await expect(browser.$("[data-testid='theme-image']")).toHaveAttribute( |
There was a problem hiding this comment.
тут можно поправить название testid на что-то типа theme-logo, чтобы была ассоциация с тем, что мы ищем именно изображение
| Например, так можно проверить сообщение об ошибке при сетевом запросе: | ||
|
|
||
| ```typescript | ||
| it("показывает сообщение при отсутствии сети", async ({ browser }) => { |
There was a problem hiding this comment.
такие примеры как будто хочется сделать рабочими, чтобы пользователь мог скопировать пример и запустить у себя. Например
it("docs search test", async ({browser}) => {
await browser.url("https://testplane.io/docs/");
await browser.throttleNetwork("offline");
await browser.$(".navbar__brand").click();
await browser.assertView("page"); // можно оставить и твой пример с ассертом. Просто на скриншотной проверки хорошо видно отсутствие интернета. Но пример должен пониматься и без запуска, да
await browser.throttleNetwork("online");
});There was a problem hiding this comment.
Согласна, что копируемый пример полезнее. Но сейчас все примеры в статье
построены на вымышленном приложении с data-testid, и часть сценариев
на публичном сайте не воспроизвести: немецкая локаль, геолокация с пунктом
выдачи, промо-акция с порогом по времени, страница с отключённым JavaScript.
Предлагаю сделать так: примеры, которые реально запускаются на публичном
сайте, перевести на нег: offline с assertView (как в твоём примере),
throttleCPU, colorScheme и viewport. Остальные оставить на вымышленном
приложении, но переписать их так, чтобы они были понятны без запуска.
Одна оговорка по примеру с assertView: у читателя нет эталонных скриншотов,
и первый прогон скопированного примера упадёт с ошибкой об отсутствии
референса, пока он не запустит с --update-refs. Если берём этот пример,
я добавлю про это строку в текст?
Такое частичное переписывание примеров подойдёт?
| browserName: "chrome", | ||
| "goog:chromeOptions": { | ||
| prefs: { | ||
| "profile.managed_default_content_settings.javascript": 2, |
There was a problem hiding this comment.
здесь бы пояснение добавить почему именно 2 (со ссылкой на спеку)
| ```typescript | ||
| it("показывает активную акцию в заданный период", async ({ browser }) => { | ||
| const clock = await browser.emulate("clock", { | ||
| now: new Date("2024-09-04T12:30:00Z"), |
There was a problem hiding this comment.
здесь не хватает важного уточнения, что при таком вызове подменяется не только время на странице, но стабаются еще и таймеры. Т.е. если на странице используется логика с setTimeout, то она не выполнится пока не будет вызван clock.tick(555).
Если пример показывает только стаб даты, то нужно явно прокинуть и параметр toFake: ["Date"]
Adds a new "Basic guides" article on emulating the user environment in Testplane.
Russian version only; the English page is a stub for now.