-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: wait page load #778
feat: wait page load #778
Conversation
e6afbd0
to
e7fc9b4
Compare
timeout = openAndWaitOpts?.timeout || config?.pageLoadTimeout, | ||
}: WaitOpts = {}, | ||
): Promise<string | void> { | ||
waitNetworkIdle &&= isChrome || isCDP; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Отключаем waitNetworkIdle
, если использование CDP Interceptor невозможно
src/browser/commands/openAndWait.ts
Outdated
if (!uri || uri === emptyPageUrl) { | ||
return session.url(uri); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На пустых страницах не нужно ничего ждать.
return session.url(uri); | ||
} | ||
|
||
const selectors = typeof selector === "string" ? [selector] : selector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typeof
вместо _.isString
, чтобы у selectors
правильно вывелся тип: string[]
|
||
const selectors = typeof selector === "string" ? [selector] : selector; | ||
|
||
const pageLoader = new PageLoader(session, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Основная логика сосредоточена в этом классе
src/browser/commands/openAndWait.ts
Outdated
return; | ||
} | ||
|
||
const shouldIgnore = isMatchPatterns(openAndWaitOpts?.ignoreNetworkErrorsPatterns, match.url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Помимо shouldThrowError
, добавил ignoreNetworkErrorPatterns
: чтобы удобно было игнорировать всякие ошибки запросов к счетчикам, которые браузер считает картинками
}); | ||
|
||
pageLoader.load(() => session.url(uri)).then(checkLoaded); | ||
}).finally(() => pageLoader.unsubscribe()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
очищаем mock
после выполнения команды. Так puppeteer
работает стабильнее, немного быстрее, не утекает память.
@@ -240,6 +240,16 @@ function buildBrowserOptions(defaultFactory, extra) { | |||
}, | |||
}), | |||
|
|||
openAndWaitOpts: option({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Возможность указать дефолты через конфиг. Например, чтобы были единые таймауты, плюс тут удобно указать игнорируемые паттерны метрики
} | ||
|
||
public async load(goToPage: () => Promise<string | void>): Promise<void> { | ||
await this.initMock(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Начинаем слушать сеть до загрузки страницы
src/utils/page-loader.ts
Outdated
this.launchSelectorsPromise(); | ||
this.launchPredicatePromise(); | ||
this.launchNetworkPromise(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
после загрузки страницы, параллельно запускаем три промиса с единым таймаутом:
selectorsPromise
: ждет появления всех селекторов, по таймауту кидает ошибкуpredicatePromise
: ждет истинности предиката, по таймауту кидает ошибкуnetworkPromise
: нужен для того, чтобы по таймауту кинуть ошибку (либо заэмиттить эвент о том, что сеть уже неактивна, если никаких запросов и не было)
const element = await this.session.$(selector); | ||
await element.waitForExist({ timeout: this.timeout }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Запись в две строчки вместо чейнинга упрощает тестирование
this.mock = await this.session.mock("**").catch(() => { | ||
logger.warn(`PageLoader: Could not create CDP interceptor`); | ||
|
||
return null; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На практике такого не встречал, но если не удастся создать CDP interceptor
, то падение теста по этой причине принесет больше проблем, чем пользы, поэтому если не удалось, игнорируем
let pendingRequests = 0; | ||
let pendingIdleTimeout: NodeJS.Timeout; | ||
this.mock.on("request", () => { | ||
this.totalRequests++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Счетчик полного количества запросов нужен, чтобы по networkIdleTimeout
обозначить, что сеть пассивная, если ни одного запроса так и не было (launchNetworkPromise
)
private mock?: Mock | null; | ||
private selectors: string[]; | ||
private predicate?: () => boolean | Promise<boolean>; | ||
private timeout: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Сколько мы готовы потратить времени на итоговую прогрузку страницы (селекторы, предикат, неактивность сети)
private predicate?: () => boolean | Promise<boolean>; | ||
private timeout: number; | ||
private waitNetworkIdle: boolean; | ||
private waitNetworkIdleTimeout: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
спустя какое время отсутствия запросов считать, что страница прогрузилась
type SessionOrigin = ReturnType<typeof mkSessionStubOrigin_>; | ||
type Session = SessionOrigin & { openAndWait(uri: string, opts: Record<string, unknown>): Promise<void> }; | ||
|
||
const mkSessionStub_ = (): Session => { | ||
return mkSessionStubOrigin_() as Session; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Создаем правильно типизированный mkSessionStub_
, у которого есть метод openAndWait
sandbox.restore(); | ||
}); | ||
|
||
const mkPromiseCheck_ = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
функция-конструктор проверок ниже: resolvedAfter_, rejectedAfter_, notResolvedAfter_, notRejectedAfer_
restore: SinonStub; | ||
}; | ||
|
||
const waitUntilMock = (condition: () => boolean, opts?: WaitForOptions): Promise<void> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitUntil
у session
застабано. Чтобы проверить логику с предикатами, нужно вернуть waitUntil
нормальное поведение
e7fc9b4
to
b3945ba
Compare
d059153
to
1928186
Compare
}); | ||
|
||
if (!this.mock) { | ||
this.markNetworkIdle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
markNetworkIdle
вернет true
, если изначально сеть была активной и стала пассивной после вызова этой функции. Здесь нам не нужно ее возвращаемое значение, потому что оно всегда будет true
.
setTimeout(() => { | ||
if (!this.totalRequests) { | ||
this.markNetworkIdle(); | ||
} | ||
}, this.waitNetworkIdleTimeout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Случай "на странице нет никаких запросов", помечаем сеть пассивной по таймауту
setTimeout(() => { | ||
const markSuccess = this.markNetworkIdle(); | ||
if (markSuccess) { | ||
logger.warn(`PageLoader: Network idle timeout`); | ||
} | ||
}, this.timeout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут - единственное место, где мы смотрим на значение. Читается это как "Если раньше сеть была активной, и мы помечаем ее пассивной по таймауту, то пишем лог. Иначе - не пишем". Это возвращаемое значение нужно только тут, и только для того, чтоб не писать лишний лог.
1928186
to
6908156
Compare
import { Matches } from "webdriverio"; | ||
import PageLoader from "../../utils/page-loader"; | ||
|
||
interface Browser { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ну этот интерфейс явно должен лежать где-то рядом с самим браузером, а не в реализации одной из его команд
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Помню, что описывал его тут отдельно, потому что была проблема с типами webdriverio
по части url
.
Но сейчас оно реализовано отдельной командой, и этих проблем нет, спокойно будет работать, если заменить этот блок на import type Browser from "../browser";
src/config/browser-options.js
Outdated
parseCli: JSON.parse, | ||
validate: value => utils.assertOptionalObject(value, "openAndWaitOpts"), | ||
map: value => { | ||
return value === defaults.openAndWaitOpts ? value : { ...defaults.openAndWaitOpts, ...value }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
почему здесь просто не оставить вторую часть условия?
setTimeout(() => { | ||
const markSuccess = this.markNetworkIdle(); | ||
if (markSuccess) { | ||
logger.warn(`PageLoader: Network idle timeout`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
для чего здесь именно warn? и само сообщение мне ни о чем не говорит
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это сообщение поважнее обычного лога
Свидетельствует о том, что "что-то пошло не так", но при этом - не так критично, как ошибка.
Само сообщение - один из аргументов называется waitNetworkIdle
, и есть еще опция timeout
. Вот это значит, что по таймауту не дождались. Можно заменить на Network was never idle in ${timeout} ms
Что сделано
Добавил метод
openAndWait
: