vyvchy
    Теми розділу

    07 · Інструменти автоматизації

    Playwright: перевірки (assertions) і автоочікування (auto-waiting)

    Зміст

    Попередня глава «Playwright: локатори і дії з елементами» закінчилася на тому, що перед кліком інструмент щось сам перевіряє. Ця глава — про те, що саме він перевіряє, чого не перевірить ніколи і як із цих двох фактів народжується або зникає (flakiness). Бо «тест упав по таймауту» — не діагноз, а симптом: у кілька, вони незалежні, і поки ви не знаєте, який саме спрацював, будь-яка правка конфіга — гадання.

    Друга складність теми — тут легко зробити помилку, яку не видно в коді. Ретрайний і ручна перевірка виглядають майже однаково, різняться на один await — і одна з них чекає, а друга ні. Так само з таймаутами: збільшити «той, що на очах» простіше, ніж зʼясувати, який саме вичерпався. Тому тема й працює як індикатор: людина розуміє механізм або підкручує числа, доки не позеленіє.

    Автоочікування: набір перевірок перед дією

    Playwright не «спить перед кліком». Перед кожною дією він виконує набір перевірок придатності (actionability), автоматично чекає, поки всі релевантні пройдуть, і лише тоді виконує запитану дію. Якщо в межах таймауту перевірки не пройшли, дія падає з TimeoutError.

    Для locator.click() перевіряється пʼять умов:

    ПеревіркаЩо означає
    локатор вказує рівно на один елементдва збіги — це помилка (strict mode), а не «беремо перший»
    Visibleмає непорожній bounding box і не має обчисленого visibility:hidden
    Stableзберігає той самий bounding box щонайменше два послідовні кадри анімації
    Receives Eventsелемент є цільовим для вказівникової події в точці дії
    Enabledне задизейблений

    Деталі, які й дають більшість непорозумінь:

    • Елементи нульового розміру й display:none невидимі, а opacity:0 вважається видимим. Прозорий елемент пройде перевірку видимості й отримає клік.
    • Задизейбленим елемент робить атрибут disabled, батьківський fieldset[disabled] або aria-disabled=true.
    • Набір перевірок залежить від дії: для дій уведення в ньому є Editable — увімкнений і не readonly.
    • «Отримує події» — це буквально перевірка цілі: при кліку в точку (10;10) інструмент дивиться, чи не перехопить клік інший елемент, зазвичай оверлей. Саме ця перевірка ловить тост, спінер або модалку, що зʼїдає клік.

    при цьому — не сам елемент, а рецепт, як його знайти: пошук відбувається в момент дії, а не в момент оголошення. Тому взагалі можливе — інструмент має право перешукати ще раз.

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Виклик дії: locator.click()

    Локатор дав рівно 1 елемент?

    Перечекати й повторити перевірки

    Visible?

    Stable?

    Receives Events?

    Enabled?

    Виконати дію

    Таймаут вичерпано?

    TimeoutError

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Ні

    Так

    Виклик дії: locator.click()

    Локатор дав рівно 1 елемент?

    Перечекати й повторити перевірки

    Visible?

    Stable?

    Receives Events?

    Enabled?

    Виконати дію

    Таймаут вичерпано?

    TimeoutError

    Опція force вимикає необовʼязкові перевірки — зокрема перестає контролювати, чи справді елемент отримує клік. Технічно це дозволено, практично — це спосіб зробити тест зеленим, приховавши дефект: якщо клік перехоплює оверлей, force не прибирає оверлей, а лише перестає про нього повідомляти. Правильна реакція — закрити оверлей у (детальніше — у главі про локатори і дії).

    Чого автоочікування не чекатиме

    Автоочікування закриває рівно одну задачу: «елемент існує, видимий і придатний до дії». Воно не знає нічого про прикладні стани — чи навішений обробник після гідратації (hydration), чи завершився фоновий запит, чи вже оновилися дані в таблиці. Кнопка може бути видимою, стабільною й увімкненою, а клік по ній — не робити нічого, бо JavaScript ще не підписався на подію. Формально дія пройшла, фактично нічого не сталося, і падіння прилетить пізніше й в іншому місці — класичний профіль .

    Спроба закрити цю прогалину «загальним» сигналом мережевої тишини — теж пастка: стан networkidle офіційна дока позначає як DISCOURAGED і прямо радить замість нього спиратися на вебасерти. Причина проста: відсутність запитів не означає, що інтерфейс домалював результат, а сторінка з постійним фоновим (polling) чи аналітикою може взагалі не дати потрібного вікна тиші.

    Що працює натомість:

    • Асерт на видимий наслідок. Перевіряйте те, що бачить користувач: що ближче тест до способу використання продукту, то більше довіри він дає. Мережева відповідь — обʼєкт синхронізації другого вибору, коли зміна в інтерфейсі надто тонка, щоб надійно за неї зачепитися.
    • Очікування конкретної відповіді — до дії, що її тригерить. очікування створюють перед кліком: інакше між дією і викликом очікування є вікно, у яке швидка відповідь прослизне непоміченою. Це знову гонка, тільки написана вручну (механіка — у главі про мережу).
    • Розуміння, на що ви синхронізуєтеся. Стан domcontentloaded настає, коли розібрано HTML, а підресурси й дані ще можуть вантажитися; load — коли завантажено й підресурси. Ні те, ні те не означає «застосунок готовий».
    • Прикладний маркер готовності. Домовтеся з командою про явний атрибут — data-hydrated, aria-busy="false" — і чекайте на нього. Це річ конкретного застосунку, а не стандарт, тому й домовленість потрібна.

    Окремо стоять анімації: вони ламають і клік (елемент їде, перевірка стабільності перечікує), і візуальне порівняння (у кадр потрапляє проміжний стан). Тому знімки роблять із заглушеними анімаціями — за це відповідає опція animations: 'disabled'; детальніше — у главі про візуальне тестування.

    Web-first асерти: перевірка, що вміє чекати

    Автоматизувати треба не лише виконання, а й перевірку: порівняння фактичного результату з очікуваним ISTQB CTAL-TAE радить робити саме асертами інструмента, а до статусу висуває дві вимоги — статус має визначатися коректно, а при failed потрібна додаткова інформація про причину (наприклад, ). Це та сама логіка, що стоїть за й артефактами падіння в главі про дебаг і репортинг.

    Web-first асерт — це асерт, який чекає, доки умова виконається. Якщо повідомлення зʼявляється за пів секунди, toBeVisible() почекає й повторить перевірку. Ключова властивість: такі асерти асинхронні, тож без await вони просто не працюють.

    // Чекає й повторює перевірку до таймауту асерту
    await expect(page.getByRole('alert')).toHaveText('Замовлення створено');
    
    // Не чекає жодної секунди: дивиться стан просто зараз і одразу повертає
    expect(await page.getByRole('alert').isVisible()).toBe(true);

    Другий рядок — один із найпоширеніших способів отримати -тест, не роблячи нічого «неправильного» на вигляд: await стоїть усередині expect, а не перед ним, тому очікування зникло.

    Асерти діляться на дві групи:

    ГрупаПоведінкаЦіна
    Ретрайні (auto-retrying: toHaveText, toBeVisible, toHaveScreenshot…)повторюють перевірку, доки вона не пройде або не вичерпається таймаут асертуобовʼязковий await
    Неретрайні (toBe, toEqual, toContain…)перевіряють будь-яку умову один разна асинхронній сторінці саме вони роблять тест флакі

    Офіційна порада однозначна: віддавати перевагу ретрайним асертам, коли це можливо, а для складніших умов брати expect.poll або expect.toPass. Неретрайні матчери не «погані» — вони просто нічого не знають про час, і на сторінці, яка показує дані асинхронно, це і є джерело недетермінізму.

    Візуальний асерт живе за тими самими правилами: toHaveScreenshot теж ретрайний і чекає, доки два послідовні знімки сторінки збіжаться, і лише тоді порівнює останній з еталоном.

    Ієрархія таймаутів

    У ранері кілька налаштовуваних таймаутів на різні задачі. Це не одна ручка з різними назвами — рівні незалежні, і саме тому питання «який таймаут спрацював» має сенс.

    РівеньЗа замовчуваннямЩо обмежує
    Таймаут тесту30 стіло тесту + налаштування фікстур + хуки beforeEach
    Таймаут асерту5 скожен ретрайний асерт окремо
    beforeAll / afterAllдорівнює тестовомуокремий таймаут; часу з тестом ці хуки не ділять
    Таймаут діївідсутнійобмежує лише таймаут тесту
    Таймаут навігаціївідсутнійте саме
    Таймаут фікстуривідсутнійте саме
    Глобальний таймаутвідсутнійувесь прогін цілком

    Головне правило дока формулює прямо: таймаут асерту не має стосунку до таймауту тесту. Наслідок читається в обидва боки. Збільшили таймаут тесту — ретрайний асерт від цього більше спроб не отримав. Збільшили таймаут асерту — тест, який уже вичерпав свої 30 секунд, це не врятує.

    Візуальний асерт вписується в ту саму схему: у toHaveScreenshot власний таймаут на , і за замовчуванням він дорівнює таймауту асерту. Такий асерт можна ще й скасувати сигналом — для тесту скасування виглядає як таймаут.

    Таймаут асерту — 5 с, з тестовим не повʼязаний

    Таймаут тесту — 30 с

    Окремий таймаут, часу з тестом не ділить

    beforeAll / afterAll

    Налаштування фікстур

    beforeEach

    Тіло тесту

    await expect(locator).toHaveText()

    Таймаут асерту — 5 с, з тестовим не повʼязаний

    Таймаут тесту — 30 с

    Окремий таймаут, часу з тестом не ділить

    beforeAll / afterAll

    Налаштування фікстур

    beforeEach

    Тіло тесту

    await expect(locator).toHaveText()

    Розділ низькорівневих таймаутів у доці супроводжується застереженням, яке варто цитувати на співбесідах: якщо ви прийшли сюди через флакі-тест, рішення майже напевно не тут. Це канонічна відповідь на «давайте поставимо таймаут більший». Для окремого справді повільного тесту передбачені важелі рівня тесту — власний таймаут через test.setTimeout() або test.slow(), а не правка глобального конфіга (конфігурація й проєкти — у главі про фікстури й конфіг).

    Ще одна межа, про яку часто плутаються: у бібліотеки й у ранера різні моделі. Бібліотека дає 30 секунд на більшість операцій. Ранер, навпаки: більшість операцій власного таймауту не мають, зате кожен тест має таймаут, який його валить. Тому досвід «у нас же було 30 секунд на клік» із бібліотечного скрипта в тести не переноситься (архітектурна різниця — у главі про архітектуру).

    Мʼякі асерти: expect.soft

    За замовчуванням впалий асерт зупиняє виконання тесту. Мʼякий асерт (expect.soft) виконання не зупиняє, але позначає тест як провалений.

    await expect.soft(page.getByTestId('total')).toHaveText('1 200 ₴');
    await expect.soft(page.getByTestId('delivery')).toHaveText('Нова пошта');
    await expect.soft(page.getByTestId('eta')).toHaveText('2 дні');

    Навіщо: за один прогін ви бачите всі три розбіжності, а не лише першу. Для сторінок із десятком полів це різниця між одним циклом розбору й трьома.

    Дві межі, які треба знати. По-перше, посеред тесту можна перевірити, чи були мʼякі падіння, і вийти раніше — це потрібно саме тому, що тест продовжує виконуватися: наступні кроки підуть на стані, у якому перевірена умова не виконалася, і сенсу в їхніх результатах може вже не бути. По-друге, мʼякі асерти працюють лише з ранером Playwright — у бібліотечному сценарії їх немає.

    expect.poll і toPass: коли ретрайного матчера не досить

    Ретрайні матчери покривають стани сторінки. А якщо чекати треба на щось поза DOM — готовність сервісу, зміну в БД, появу листа? Для цього є дві обгортки.

    expect.poll перетворює будь-який синхронний expect на асинхронний полінг. Канонічний приклад у самій доці — полінг до статусу HTTP 200:

    await expect.poll(async () => {
      const response = await page.request.get('/api/health');
      return response.status();
    }, {
      timeout: 10_000,            // за замовчуванням 5 с; 0 вимикає стелю
      intervals: [1_000, 2_000, 10_000],
    }).toBe(200);

    Дефолтні інтервали — [100, 250, 500, 1000] мс, після чого повторюється останній. Дефолтний таймаут — 5 секунд, а timeout: 0 вимикає його повністю. Остання опція виглядає як надійність, а насправді перетворює фейл на зависання: тест, який не може впасти, перестає бути перевіркою.

    expect.toPass повторює блок коду, доки він не пройде цілком, — зручно, коли всередині кілька дій і асертів. Тут є окрема пастка, названа в доці прямо: toPass не успадковує налаштований таймаут асерту, за замовчуванням у нього таймаут 0, тож задавати його треба явно.

    Скільки ставити? Тут корисна порада з протилежного боку: стеля має бути навмисно високою, а не «реалістичною» — маленький інтервал полінгу й так обмежує втрачений час, тож висока стеля майже не коштує нічого й прибирає хибні падіння на повільному CI. Але скінченною вона бути зобовʼязана. Ще одна деталь на подумати: функція всередині полінгу викликається багато разів, тож вона мусить бути безпечною для повторення — полінг на створенні замовлення створить N замовлень.

    Мʼякий асерт із полінгом поєднується: тоді тест продовжиться навіть при невдачі всередині poll — і фейл треба ловити окремо.

    Чому waitForTimeout — антипатерн

    Метод page.waitForTimeout() існує й робить рівно те, що обіцяє. Але офіційна дока позначає його як Discouraged і формулює правило без помʼякшень: ніколи не чекати таймаутом у продакшн-тестах, бо тести, що чекають час, флакі за побудовою. Дозволене застосування назване одне — дебаг.

    Аргумент проти (hard wait) — не стилістичний, а арифметичний, і дока Selenium розкладає його на дві половини. Код не може знати, скільки саме треба чекати: замало — тест падає на повільному прогоні; забагато, та ще й у кожному потрібному місці — тривалість сесії стає непідйомною. Пауза синхронізується з годинником, а не із застосунком, тому вона не буває «правильною» — лише випадково достатньою. Там же названо й корінь проблеми: гонка між браузером і кодом тесту — одна з головних причин флакі-тестів.

    Заміна голої паузи — не довша пауза, а умова. Дока перелічує саме це: дії локатора й вебасерти, які чекають автоматично, плюс сигнали — мережеві події, поява селектора. Тобто весь інструментарій, розібраний вище: автоочікування перед дією, ретрайний асерт на наслідок, expect.poll для складнішої умови.

    // Антипатерн: тест синхронізується з годинником
    await page.getByRole('button', { name: 'Зберегти' }).click();
    await page.waitForTimeout(3_000);
    await expect(page.getByRole('alert')).toBeVisible();
    
    // Те саме, але синхронізовано із застосунком
    await page.getByRole('button', { name: 'Зберегти' }).click();
    await expect(page.getByRole('alert')).toHaveText('Збережено');

    Один випадок варто назвати чесно, і разом із його статусом. Коли перевіряють відсутність зміни — «за N секунд нічого не сталося», — умови «нічого не сталося» в асерті не існує, тож рука тягнеться до паузи. Наша практика (не канон): джерела такого винятку не формулюють — ні дока Selenium, ні дока Playwright його не називають, — тому подавати його правилом не можна. Чесніший хід: знайти спостережуваний сигнал, після якого відсутність зміни вже щось означає (прихід конкретної відповіді, завершення запиту), і перевіряти стан після нього; час лишається останнім засобом, а не виправданим винятком.

    Заборона на голі паузи не є особливістю одного інструмента. У CodeceptJS очікування так само вбудоване в дії, а невдалий крок повторюється автоматично — рівень ретраю там нижчий за тест (деталі — у главі про CodeceptJS). Політика ретраїв і засоби боротьби з флаком на рівні конфігурації — у главі «Боротьба з флаком засобами інструмента», а браузерна механіка синхронізації — у главі «Практичні сценарії AQA: флак і синхронізація».

    Типові помилки

    • «Упало по таймауту — збільшимо таймаут тесту» → якщо вичерпався таймаут асерту, тестовий не додасть жодної спроби: рівні не повʼязані. Дока сама попереджає, що для флаку рішення майже напевно не в низькорівневих таймаутах.
    • «Кнопка видима, а клік не працює — баг застосунку» → зазвичай спрацювала перевірка «отримує події»: клік перехоплює оверлей, тост або спінер. Це корисне падіння, і лікується воно закриттям оверлея в передумові, а не force.
    • «Елемент прозорий, отже, невидимий»opacity:0 вважається видимим. Невидимими роблять нульовий розмір, display:none і visibility:hidden.
    • expect(await locator.isVisible()).toBe(true) → виглядає як асерт із очікуванням, а насправді не чекає жодної секунди: await стоїть усередині expect, а не перед ним.
    • «Асерт без await — просто негарно» → ретрайні асерти асинхронні: без await вони не працюють, і тест зеленіє, нічого не перевіривши.
    • «toPass візьме мій expect.timeout із конфіга» → не візьме: у toPass за замовчуванням таймаут 0, його треба задати явно.
    • «Мʼякі асерти не впали — тест зелений» → мʼякий асерт не зупиняє виконання, але позначає тест провальним; а наступні кроки йдуть на стані, у якому умова не виконалася.
    • «timeout: 0 у полінгу — це надійніше» → це фейл, який перетворюється на зависання. Стеля має бути високою, але скінченною.
    • «Дочекаюсь networkidle — і сторінка готова» → стан позначено як DISCOURAGED, і дока радить вебасерти: тиша в мережі не означає, що інтерфейс домалював результат.
    • «Перед скріншотним асертом треба пауза, бо він ловить кадр посеред анімації» → не треба: toHaveScreenshot ретрайний і сам чекає, доки два послідовні знімки збіжаться, а анімації в ньому вимкнені за замовчуванням — скінченні перемотуються до кінця, нескінченні скасовуються до початкового стану.

    Підсумок

    • Автоочікування — про придатність елемента, не про готовність застосунку. Перед дією перевіряється однозначність локатора, видимість, стабільність, отримання подій і увімкненість; гідратація, фонові запити й оновлення даних до цього переліку не входять.
    • Ретрайний асерт чекає, ручна перевірка — ні. await expect(locator).toHaveText() повторює перевірку до таймауту асерту; expect(await locator.isVisible()) дивиться стан просто зараз. Ретрайні асерти асинхронні — без await вони не працюють.
    • Таймаути незалежні. Тест — 30 секунд (тіло + + beforeEach), асерт — 5 секунд, beforeAll/afterAll — окремо, дії/навігація/фікстура/глобальний — за замовчуванням відсутні. Таймаут асерту не має стосунку до таймауту тесту.
    • Складніша умова — обгортка, а не пауза. expect.poll для одного значення, toPass для блоку; стеля висока, але скінченна, і toPass свій таймаут не успадковує.
    • waitForTimeout — лише для дебагу. Тести, що чекають час, флакі за побудовою; заміна — умова: автоочікування, вебасерт, сигнал. Спостереження відсутності зміни джерельним винятком не є — це наша практика, і сигнал шукають навіть там.

    Можливі питання

    • «Що таке auto-waiting і чого він не чекає?» — інтервʼюер дивиться, чи ви розводите два рівні: придатність елемента до дії (це інструмент) і готовність застосунку (це ваш явний сигнал). Сильна відповідь називає кілька перевірок поіменно й одразу — межу.
    • «Які таймаути ти знаєш?» — перевіряють саме розділення рівнів. «30 секунд» без назви рівня — половина відповіді; очікують «тест — 30, асерт — 5, і вони не повʼязані».
    • «Чим await expect(locator).toBeVisible() відрізняється від expect(await locator.isVisible()) — питання на розуміння ретраю. Відповідь у одному реченні: перший чекає й повторює, другий дивиться один раз і не чекає.
    • «Чому не можна просто поставити паузу? А коли можна?» — чекають дві речі: аргумент «тести, що чекають час, флакі за побудовою» (і що це слова доки, а не ваш смак) плюс чесний виняток — спостереження відсутності зміни та дебаг.
    • «Коли доречний expect.soft — хочуть побачити, що ви розумієте ціну: тест іде далі, тож мʼякий асерт годиться для набору незалежних перевірок одного екрана, а не там, де наступні кроки залежать від перевіреної умови.
    • «Чим expect.poll відрізняється від toPass — одне значення проти блоку коду; бонусні бали за згадку, що toPass не успадковує таймаут асерту.
    • «Тест падає на кліку, локатор правильний — що робиш?» — оцінюють порядок розбору, а не здогад: який саме таймаут спрацював → чи асерт був ретрайний → чи не перехоплює клік оверлей → і лише потім розмова про числа. Відповідь «ставлю force / збільшую таймаут» тут читається як червоний .

    Джерела

    Автоочікування: набір перевірок перед дією

    • Playwright — Auto-waiting (actionability) — перелік перевірок придатності перед дією, дослівні означення Visible/Stable/Receives Events/Enabled/Editable, TimeoutError і наслідок опції force.
    • Playwright — Locators — локатор як рецепт пошуку, який резолвиться в момент дії.

    Чого автоочікування не чекатиме

    Web-first асерти: перевірка, що вміє чекати

    • ISTQB® CTAL-TAE Syllabus v2.0 — §6.1.1: порівняння фактичного з очікуваним найкраще робити асертами інструмента; статус має бути коректним, а при failed потрібна додаткова інформація про причину.
    • Playwright — Best Practices — web-first асерт чекає й повторює; isVisible() усередині expect «не чекає жодної секунди».
    • Playwright — Test assertions (expect.poll, toPass) — поділ на ретрайні й неретрайні матчери, обовʼязковий await, порада надавати перевагу ретрайним.
    • Playwright — API: PageAssertionstoHaveScreenshot чекає збігу двох послідовних знімків.

    Ієрархія таймаутів

    • Playwright — Timeouts — кілька незалежних таймаутів, 30 с на тест і що в них входить, окремий таймаут beforeAll/afterAll, відсутні за замовчуванням низькорівневі таймаути, застереження про флак, test.setTimeout() і test.slow().
    • Playwright — Test assertions (expect.poll, toPass) — таймаут ретрайного асерту 5 с за замовчуванням.
    • Playwright — Library — модель бібліотеки: 30 с на більшість операцій проти таймауту на тест у ранері.
    • Playwright — API: PageAssertions — візуальний асерт має власний таймаут; скасування сигналом виглядає як таймаут.

    Мʼякі асерти: expect.soft

    • Playwright — Test assertions (expect.poll, toPass) — звичайний асерт зупиняє тест, мʼякий не зупиняє, але робить його провальним; перевірка стану мʼяких падінь посеред тесту; обмеження «лише з ранером».

    expect.poll і toPass: коли ретрайного матчера не досить

    • Playwright — Test assertions (expect.poll, toPass)expect.poll перетворює синхронний асерт на полінг, приклад із HTTP 200, дефолтний таймаут 5 с і timeout: 0, інтервали [100, 250, 500, 1000], toPass із власним таймаутом 0, поєднання з expect.soft.
    • Martin Fowler — Eradicating Non-Determinism in Tests — стеля очікування має бути навмисно високою, бо малий інтервал полінгу обмежує втрачений час; кращий за полінг, але таймаут потрібен і йому.

    Чому waitForTimeout — антипатерн

    • Playwright — page.waitForTimeout (Discouraged) — метод позначено як Discouraged: «Never wait for timeout in production», тести, що чекають час, флакі за побудовою; дозволено лише для дебагу; заміна — дії локатора, вебасерти й сигнали.
    • Selenium — Waiting Strategies — подвійна вада sleep (замало / непідйомно довго), гонка як одна з головних причин флакі-тестів, два механізми синхронізації як «better».
    • Martin Fowler — Eradicating Non-Determinism in Tests — пряма заборона: «Never use bare sleeps to wait for asynchonous responses: use a callback or polling».
    • CodeceptJS — Basics — очікування вбудоване в дії, невдалий крок повторюється автоматично.

    Типові помилки

    • Playwright — Timeouts — незалежність рівнів («Assertion timeout is unrelated to the test timeout») і застереження, що для флаку рішення шукати не тут.
    • Playwright — Auto-waiting (actionability) — перевірка «отримує події» проти оверлея, opacity:0 як видимий, наслідок force.
    • Playwright — Best Practices — ручна перевірка isVisible() не чекає жодної секунди.
    • Playwright — Test assertions (expect.poll, toPass) — обовʼязковий await для ретрайних асертів, таймаут 0 у toPass, семантика expect.soft, timeout: 0 у полінгу.
    • Playwright — class Pagenetworkidle як DISCOURAGED.
    • Playwright — API: PageAssertionstoHaveScreenshot чекає збігу двох послідовних знімків; анімації вимкнені за замовчуванням (скінченні перемотуються, нескінченні скасовуються до початкового стану).

    Підсумок

    Можливі питання

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.