ЁЯзк рдзрдбрд╛ 10 тАФ UI testing: рдлрд▓рдХ рдЕрдЬреВрди рдЙрднрд╛ рдЖрд╣реЗ рдХрд╛?
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреАрд▓: lesson-09-performance ┬╖ рдкреБрдвреАрд▓: lesson-11-talking-to-backends
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдлрд▓рдХ рди рдореЛрдбрддрд╛ рддреЛ рдХрд╕рд╛ рдмрджрд▓рд╛рдпрдЪрд╛: UI рд╕рд╛рдареА test pyramid (unit, component, end-to-end, visual), рдХрд╛рдп assert рдХрд░рд╛рдпрдЪреЗ (class names рдирд╛рд╣реА, рддрд░ roles рдЖрдгрд┐ labels), рдЖрдгрд┐ рдЖрдзреАрдЪ рдпрд╛ repo рд╡рд░ рдкрд╣рд╛рд░рд╛ рджреЗрдгрд╛рд░рд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА dependency рд╢рд┐рд╡рд╛рдпрдЪрд╛ smoke test. рдЦрд▒реНрдпрд╛ files:
- ui/smoke_test.py тАФ рджрд╣рд╛ рддрдкрд╛рд╕рдгреНрдпрд╛, рдХреЛрдгрддреАрд╣реА dependency рдирд╛рд╣реА
- ui/tests/board.spec.js тАФ Playwright рдордзреНрдпреЗ рддреЛрдЪ рд╣реЗрддреВ
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдХреЛрдгреАрд╣реА рдлрд▓рдХ рдкреБрдиреНрд╣рд╛ рд░рдВрдЧрд╡рдгреНрдпрд╛рдЖрдзреА, рддреЛ рдЕрдЬреВрди рдЪрд╛рд▓рддреЛ рдХрд╛ рд╣реЗ рдХреЛрдгреАрддрд░реА рддрдкрд╛рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ. рддрдкрд╛рд╕рдгреНрдпрд╛рдВрдЪреЗ рдЪрд╛рд░ рдкреНрд░рдХрд╛рд░ рдЖрд╣реЗрдд, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХрд╛рдЪреЗ рдкреНрд░рдорд╛рдг рд╡реЗрдЧрд╡реЗрдЧрд│реЗ рд╣рд╡реЗ:
- Unit ЁЯФм тАФ рдПрдХ pure function рд╡реЗрдЧрд│реЗ test рдХрд░рд╛. Test data рд╕рд╣
StudentCard(s): рддреЗ рдирд╛рд╡ рдШрд╛рд▓рддреЗ рдХрд╛? рдЬрд▓рдж, рднрд░рдкреВрд░. - Component ЁЯзй тАФ рдмрдирд╛рд╡рдЯ data рд╕рд╣ рдПрдХ рддреБрдХрдбрд╛ render рдХрд░рд╛ рдЖрдгрд┐ DOM рд╡рд░ assert рдХрд░рд╛ ("Enrol рдирд╛рд╡рд╛рдЪреЗ button рдЖрд╣реЗ"). рддрд░реАрд╣реА рдЬрд▓рдж; рдЦрд░реА рдЬреЛрдбрдгреА рдкрдХрдбрддреЗ.
- End-to-end ЁЯзн тАФ рдПрдХ рдЦрд░рд╛ browser рдлрд▓рдХ рдЙрдШрдбрддреЛ, form рднрд░рддреЛ, Enrol рд╡рд░ click рдХрд░рддреЛ, рдЖрдгрд┐ card рджрд┐рд╕рддреЗ рдХрд╛ рддреЗ рддрдкрд╛рд╕рддреЛ. рдордВрдж, рдХрдзреАрдХрдзреА flaky, рдЕрдореВрд▓реНрдп. рдореЛрдЬрдХреЗрдЪ рд╕реЛрдиреЗрд░реА рдорд╛рд░реНрдЧ рдареЗрд╡рд╛.
- Visual ЁЯУ╕ тАФ рдлрд▓рдХрд╛рдЪрд╛ screenshot рдШреНрдпрд╛ рдЖрдгрд┐ рдХрд╛рд▓рдЪреНрдпрд╛рд╢реА diff рдХрд░рд╛, рдореНрд╣рдгрдЬреЗ рдХреЛрдгреАрд╣реА рдмрджрд▓рд╛рдпрдЪрд╛ рд╣реЗрддреВ рдирд╕рд▓реЗрд▓реА CSS рдкрдХрдбрд▓реА рдЬрд╛рдИрд▓.
рдЖрдгрд┐ рдХрд╛рдп assert рдХрд░рд╛рдпрдЪреЗ рдпрд╛рдЪрд╛ рдПрдХрдЪ рдирд┐рдпрдо: user рдЬрд╕реЗ рд╡рд┐рдЪрд╛рд░рддреЛ рддрд╕реЗ рд╡рд┐рдЪрд╛рд░рд╛.
"Enrol рдирд╛рд╡рд╛рдЪреЗ button", "Name рдЕрд╕реЗ label рдЕрд╕рд▓реЗрд▓реЗ field" тАФ .btn-primary рдХрд┐рдВрд╡рд╛
#f3 > div:nth-child(2) рдирд╛рд╣реА. User рдЪреНрдпрд╛ рднрд╛рд╖реЗрдд рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ tests redesigns рдордзреВрди
рдЯрд┐рдХрддрд╛рдд рдЖрдгрд┐ accessibility рддрдкрд╛рд╕рдгреА рдореНрд╣рдгреВрдирд╣реА рдХрд╛рдо рдХрд░рддрд╛рдд: test рд▓рд╛ control рддреНрдпрд╛рдЪреНрдпрд╛
рдирд╛рд╡рд╛рдиреЗ рд╕рд╛рдкрдбрдд рдирд╕реЗрд▓, рддрд░ screen reader рд▓рд╛рд╣реА рд╕рд╛рдкрдбрдд рдирд╛рд╣реА (рдзрдбрд╛ 06).
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart TB
u["ЁЯФм unit тАФ many, milliseconds<br/>StudentCard(s) puts the name in"]
c["ЁЯзй component тАФ some<br/>the form renders, the button is named Enrol"]
e["ЁЯзн end-to-end тАФ few, slow<br/>a real browser enrols a student"]
v["ЁЯУ╕ visual тАФ a safety net<br/>screenshot diffs catch unintended CSS"]
u --> c --> e --> v
rule["assert by role and label тАФ never by class name"]
тЭУ рдХрд╛рдп
- Pyramid: рддрд│рд╛рд╢реА рдЦреВрдк рд╕реНрд╡рд╕реНрдд tests, рд╡рд░ рдереЛрдбреЗ рдорд╣рд╛рдЧ. рдЙрд▓рдЯреЗ pyramids (рд╕рдЧрд│реЗ end-to-end) рдордВрдж рдЖрдгрд┐ flaky рдЕрд╕рддрд╛рдд; рд╡рд░рдЪрд╛ рднрд╛рдЧрдЪ рдирд╕реЗрд▓ рддрд░ integration bugs рдкреБрдвреЗ рдЬрд╛рддрд╛рдд.
- Tools: Vitest/Jest (unit), Testing Library (component тАФ рддреНрдпрд╛рдЪреЗ рд╕рдВрдкреВрд░реНрдг
рддрддреНрддреНрд╡рдЬреНрдЮрд╛рди
getByRole/getByLabelTextрдЖрд╣реЗ), Playwright рдХрд┐рдВрд╡рд╛ Cypress (end-to-end), Playwright/Percy (visual). - рдЖрдкрд▓реА dependency-рд╢рд┐рд╡рд╛рдпрдЪреА рдЖрд╡реГрддреНрддреА:
ui/smoke_test.pypage fetch рдХрд░рддреЛ рдЖрдгрд┐ рд░рдЪрдиреЗрд╡рд░ assert рдХрд░рддреЛ (lang, рдПрдХрдЪh1, inputs рд╕рд╛рдареА labels, images рд╡рд░ alt, skip link, live region), CSS рдордзрд▓реЗ tokens, рдЖрдгрд┐ API рдЪреНрдпрд╛ рдпрд╢ рдЖрдгрд┐ рдЕрдкрдпрд╢рд╛рдЪреЗ рдЖрдХрд╛рд░. рджрд╣рд╛ рддрдкрд╛рд╕рдгреНрдпрд╛, рдПрдХ exit code тАФ CI рд╕рд╛рдареА рдЕрдЧрджреА рдпреЛрдЧреНрдп. - CI рдордзреНрдпреЗ (CI/CD рд╢рд╛рд│рд╛): server рд╕реБрд░реВ рдХрд░рд╛, рддрдкрд╛рд╕рдгреНрдпрд╛ рдЪрд╛рд▓рд╡рд╛, non-zero exit рд╡рд░ build fail рдХрд░рд╛.
- Accessibility testing: library рдореНрд╣рдгреВрди axe-core, рдХрд┐рдВрд╡рд╛ CI рдордзреНрдпреЗ Lighthouse тАФ automated tools рд╕рд╛рдзрд╛рд░рдг рдПрдХ рддреГрддреАрдпрд╛рдВрд╢ рдкрдХрдбрддрд╛рдд; рдмрд╛рдХреАрдЪреЗ keyboard рд╡рд░реВрди рдХреЗрд▓реЗрд▓реА рдлреЗрд░реА рдкрдХрдбрддреЗ.
- Flakiness: рдард░рд╛рд╡рд┐рдХ milliseconds рд╕рд╛рдареА рдХрдзреАрдЪ рдерд╛рдВрдмреВ рдирдХрд╛, рдкрд░рд┐рд╕реНрдерд┐рддреАрдЪреА (рдПрдЦрд╛рджрд╛ element рджрд┐рд╕рдгреНрдпрд╛рдЪреА) рд╡рд╛рдЯ рдкрд╛рд╣рд╛.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг UI рд╣рд╛ рд▓реЛрдХ рд╕рдЧрд│реНрдпрд╛рдд рдЬрд╛рд╕реНрдд рд╡реЗрд│рд╛ рдмрджрд▓рддрд╛рдд рдЖрдгрд┐ рдЬреНрдпрд╛рд╡рд░ рд╕рдЧрд│реНрдпрд╛рдд рдХрдореА рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рддрд╛рдд рдЕрд╕рд╛ рдерд░ рдЖрд╣реЗ, рдЖрдгрд┐ "рдореА рдЗрдХрдбреЗрддрд┐рдХрдбреЗ click рдХреЗрд▓реЗ рдЖрдгрд┐ рдареАрдХ рджрд┐рд╕рд▓реЗ" рд╣реЗ рдПрдХрд╛ рдорд╛рдгрд╕рд╛рдкрд▓реАрдХрдбреЗ рдЪрд╛рд▓рдд рдирд╛рд╣реА. рджреЛрди рд╕реЗрдХрдВрджрд╛рдВрдд рдЪрд╛рд▓рдгрд╛рд░рд╛ рджрд╣рд╛-рддрдкрд╛рд╕рдгреНрдпрд╛рдВрдЪрд╛ smoke test рд▓рд╛рдЬрд┐рд░рд╡рд╛рдгреНрдпрд╛ рдкреНрд░рдХрд╛рд░рдЪреЗ bugs рдерд╛рдВрдмрд╡рддреЛ: рд╣рд░рд╡рд▓реЗрд▓реЗ label, рдореЛрдбрд▓реЗрд▓рд╛ endpoint, token рдордзрд▓реА typo.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
ui/smoke_test.py рдлрдХреНрдд urllib рдЖрдгрд┐ re рд╡рд╛рдкрд░рддреЛ: рддреЛ HTML рдЪреЗ semantics, CSS
рдЪреЗ tokens рдЖрдгрд┐ focus style assert рдХрд░рддреЛ, рдЖрдгрд┐ API рд▓рд╛ рджреЛрдирджрд╛ hit рдХрд░рддреЛ тАФ рдПрдХрджрд╛
students рдЪреА рдЕрдкреЗрдХреНрд╖рд╛ рдареЗрд╡реВрди, рдПрдХрджрд╛ рдЪреБрдХреАрдЪреНрдпрд╛ form рд╕рд╛рдареА 400 рдЪреА рдЕрдкреЗрдХреНрд╖рд╛ рдареЗрд╡реВрди.
ui/tests/board.spec.js рддреЗрдЪ рджреЛрди рдкреНрд░рд╡рд╛рд╕ Playwright рдордзреНрдпреЗ рджрд╛рдЦрд╡рддреЛ, getByRole
рдЖрдгрд┐ getByLabel рд╡рд╛рдкрд░реВрди рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 ui/mock_api.py &
python3 ui/smoke_test.py; echo "exit code: $?" # ЁЯОЙ the board stands ┬╖ 0
# 1) break something and watch it fail:
# remove alt="" from the card <img> in index.html тЖТ тЭМ images have alt, exit 1
# 2) add YOUR check to smoke_test.py (3 lines): the enrol form has a submit button
# check("form has a submit button", 'type="submit"' in html)
# 3) add a check that the dark theme tokens exist for [data-theme="dark"]
# 4) read ui/tests/board.spec.js and notice: getByRole('button', {name: 'Enrol'}), getByLabel('Name') тАФ
# the same words a screen reader would say
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
рдирд┐рд░реЛрдЧреА рдлрд▓рдХрд╛рд╡рд░ ЁЯОЙ the board stands рдЖрдгрд┐ exit code 0; alt рдХрд╛рдврд▓реНрдпрд╛рд╡рд░ рдирд╛рд╡
рдЕрд╕рд▓реЗрд▓реА тЭМ рдУрд│ рдЖрдгрд┐ exit 1. рддреБрдордЪреНрдпрд╛ рджреЛрди рдирд╡реАрди рддрдкрд╛рд╕рдгреНрдпрд╛ рдпрд╛рджреАрдд рджрд┐рд╕рддрд╛рдд рдЖрдгрд┐ pass
рд╣реЛрддрд╛рдд.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рджрд╣рд╛ рд╕реЗрдХрдВрджрд╛рдВрдЪреНрдпрд╛ рддрдкрд╛рд╕рдгреНрдпрд╛ рд╕рдЧрд│реНрдпрд╛рдд рдЬрд╛рд╕реНрдд рддреНрд░рд╛рд╕ рджреЗрдгрд╛рд▒реНрдпрд╛ рдЪреБрдХрд╛ рдкрдХрдбрддрд╛рдд, рдЖрдгрд┐ user рдЪреНрдпрд╛ рднрд╛рд╖реЗрдд рд▓рд┐рд╣рд┐рд▓реЗрд▓реЗ tests accessibility tests рдореНрд╣рдгреВрдирд╣реА рдХрд╛рдо рдХрд░рддрд╛рдд.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- class names рдХрд┐рдВрд╡рд╛ DOM paths рд╡рд░ assert рдХрд░рдгреЗ тАФ рдкреНрд░рддреНрдпреЗрдХ redesign suite рдореЛрдбрддреЛ
- рдлрдХреНрдд end-to-end tests (рдордВрдж, flaky) рдХрд┐рдВрд╡рд╛ рдлрдХреНрдд unit tests (integration bugs рдкреБрдвреЗ рдЬрд╛рддрд╛рдд)
- рдкрд░рд┐рд╕реНрдерд┐рддреАрдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рдРрд╡рдЬреА
sleep(2000) - рдореЛрдареНрдпрд╛ HTML рдЧрдареНрдареНрдпрд╛рдВрдЪреЗ snapshot tests, рдЬреЗ рдмрджрд▓рд▓реНрдпрд╛рд╡рд░ рдХреЛрдгреАрдЪ рд╡рд╛рдЪрдд рдирд╛рд╣реА
- рддреБрдордЪреНрдпрд╛ рд╡рд╛рдЧрдгреБрдХреАрдРрд╡рдЬреА framework test рдХрд░рдгреЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рд╕рдЧрд│реНрдпрд╛рдд рд╡рд╛рд░рдВрд╡рд╛рд░ рд╣реЛрдгрд╛рд░реЗ, рд╕рдЧрд│реНрдпрд╛рдд рдЬрд╛рд╕реНрдд рджрд┐рд╕рдгрд╛рд░реЗ regressions front end рдордзреНрдпреЗрдЪ рд░рд╛рд╣рддрд╛рдд. CI рдордзрд▓рд╛ рдЫреЛрдЯрд╛рд╕рд╛ smoke test рд╣рд╛ рдХреЛрдгреАрд╣реА UI repo рдордзреНрдпреЗ рдЬреЛрдбреВ рд╢рдХрдгрд╛рд░реЗ рд╕рдЧрд│реНрдпрд╛рдд рдореМрд▓реНрдпрд╡рд╛рди testing рдЖрд╣реЗ тАФ рдореНрд╣рдгреВрдирдЪ рд╣рд╛ repo рддреНрдпрд╛рдЪреНрдпрд╛рд╕рд╣ рдпреЗрддреЛ.
тПня╕П рдкреБрдвреЗ
рдлрд▓рдХ test рдЭрд╛рд▓рд╛ рдЖрд╣реЗ; рдЖрддрд╛ рддреНрдпрд╛рд▓рд╛ рдЬрдЧрд╛рд╢реА рд╕реБрд░рдХреНрд╖рд┐рддрдкрдгреЗ рдмреЛрд▓реВ рджреНрдпрд╛: backends рд╢реА рдмреЛрд▓рдгреЗ тАФ CORS, tokens, loading рдЖрдгрд┐ error states, XSS.
git checkout lesson-11-talking-to-backends