QA How-To
Playwright keyboard press: Examples and Best Practices
Explore playwright keyboard press examples for Enter, Escape, Tab, shortcuts, listboxes, frames, focus, debugging, and reliable TypeScript tests in CI.
21 min read | 2,535 words
TL;DR
Reliable keyboard examples explicitly establish focus, send one supported Playwright key or shortcut, and verify the feature outcome. Prefer locator.press() for a known element and page.keyboard.press() for Tab order or global shortcuts.
Key Takeaways
- Tie every keyboard example to a recipient, key string, and observable outcome.
- Use locator.press() for known controls and page.keyboard.press() for focus journeys.
- Assert focus restoration after Escape closes modal or transient UI.
- Test arrow navigation through semantic selected state, not direct DOM selection.
- Use ControlOrMeta only when the product follows platform command conventions.
- Release held modifiers in finally blocks to prevent state leakage.
- Use traces and web-first assertions instead of delay-based CI fixes.
The most useful playwright keyboard press examples do more than show press('Enter') in isolation. They establish the correct focus, choose between locator.press() and page.keyboard.press(), exercise a real keyboard contract, and assert the result a user would observe.
This recipe-based guide covers forms, dialogs, focus order, command shortcuts, listbox navigation, rich editors, frames, event verification, reusable helpers, and CI diagnosis. The TypeScript examples are self-contained Playwright Test cases, so you can run them without inventing unsupported APIs.
TL;DR
| Example | Recommended call | Assertion |
|---|---|---|
| Submit from a field | field.press('Enter') |
Status, response, or navigation |
| Dismiss a dialog | dialog.press('Escape') or focused control press |
Dialog becomes hidden |
| Verify focus order | page.keyboard.press('Tab') |
toBeFocused() |
| Select all portably | editor.press('ControlOrMeta+A') |
Selection-dependent outcome |
| Navigate a listbox | input.press('ArrowDown') |
Selected option state |
| Hold a modifier | keyboard.down() and keyboard.up() |
Final editor state |
For a known element, prefer locator.press(). For natural focus movement or a page-level shortcut, use page.keyboard.press() after making the starting focus explicit.
1. playwright keyboard press examples Reference Matrix
A keyboard test has three parts: recipient, key, and outcome. Choosing all three before writing code prevents many false positives.
| User behavior | Recipient strategy | Key string | Strong result |
|---|---|---|---|
| Submit search | Labeled input locator | Enter |
Results region or URL changes |
| Cancel inline edit | Editable row locator | Escape |
Original value is restored |
| Move through checkout | Current page focus | Tab |
Expected next control is focused |
| Open command menu | Page with neutral focus | ControlOrMeta+K |
Named dialog becomes visible |
| Pick next suggestion | Combobox locator | ArrowDown then Enter |
Option state and field value change |
| Delete selected text | Editor locator | ControlOrMeta+A then Backspace |
Editor becomes empty |
| Extend a selection | Focused editor plus page keyboard | Held Shift and arrows |
Expected text is removed or replaced |
The locator form resolves and focuses a target before pressing. The page keyboard sends input to the current focus. This difference is not merely style. It determines whether the test models direct interaction with one component or the user's focus journey across components.
Use fill() for regular text, then reserve press() for keys whose event semantics matter. If the application must see each character, locator.pressSequentially() is available. If you need a modifier held across several steps, use keyboard.down() and keyboard.up() with reliable cleanup.
The Playwright keyboard press guide explains the API model in depth. The rest of this article emphasizes independent test recipes and review criteria.
2. Press Enter to Submit a Form
Enter submission is a high-value scenario because it validates parity between keyboard and pointer workflows. The assertion should prove business behavior, not only that a key event fired.
import { test, expect } from '@playwright/test';
test('submits a support search with Enter', async ({ page }) => {
await page.setContent(`
<form id="support-search">
<label for="query">Search support</label>
<input id="query" name="query">
<button type="submit">Search</button>
</form>
<p role="status">No search yet</p>
<script>
document.querySelector("#support-search").addEventListener("submit", event => {
event.preventDefault();
const value = new FormData(event.target).get("query");
document.querySelector("[role=status]").textContent =
"Showing results for " + value;
});
</script>
`);
const query = page.getByLabel('Search support');
await query.fill('trace viewer');
await query.press('Enter');
await expect(page.getByRole('status'))
.toHaveText('Showing results for trace viewer');
});
The field locator makes the recipient unambiguous. The status assertion proves the form's submit pathway ran. A weaker assertion such as toHaveValue('trace viewer') would only confirm setup and could pass even if Enter did nothing.
When Enter causes navigation, use await expect(page).toHaveURL(...) or assert destination content. When it creates a request but stays on the page, assert the final UI. A successful HTTP response alone does not prove the application rendered or accepted the result.
Add a pointer submission test only if it covers a distinct risk. One parameterized test can verify both activation paths, but separate tests often give clearer reports. The essential accessibility contract is that the keyboard path reaches the same valid result without requiring a mouse.
3. Press Escape to Dismiss a Dialog or Cancel Editing
Escape should restore a stable previous state. That may mean hiding a dialog, discarding a draft, closing a menu, or returning focus to the opener. Test all outcomes that are part of the component contract.
import { test, expect } from '@playwright/test';
test('dismisses the command dialog with Escape', async ({ page }) => {
await page.setContent(`
<button id="open">Open commands</button>
<section role="dialog" aria-label="Commands" hidden>
<label for="command">Command</label>
<input id="command">
</section>
<script>
const opener = document.querySelector("#open");
const dialog = document.querySelector("[role=dialog]");
const input = document.querySelector("#command");
opener.addEventListener("click", () => {
dialog.hidden = false;
input.focus();
});
input.addEventListener("keydown", event => {
if (event.key === "Escape") {
dialog.hidden = true;
opener.focus();
}
});
</script>
`);
const opener = page.getByRole('button', { name: 'Open commands' });
await opener.click();
const dialog = page.getByRole('dialog', { name: 'Commands' });
const command = dialog.getByLabel('Command');
await expect(command).toBeFocused();
await command.press('Escape');
await expect(dialog).toBeHidden();
await expect(opener).toBeFocused();
});
This example checks both dismissal and focus restoration. A dialog that disappears while leaving focus on a hidden descendant remains broken for keyboard users.
For an inline editor, assert the restored value and view mode. If Escape is supposed to preserve a draft, assert that instead. The key's meaning comes from the product specification, so generic helpers should not assume every Escape press closes the nearest surface.
Avoid calling page.keyboard.press('Escape') immediately after a long sequence without checking focus. An unexpected toast, menu, or nested dialog may consume it. A locator-scoped press makes ownership obvious when one control is responsible for cancellation.
4. Validate Tab and Shift+Tab Focus Order
Tab order is one of the cases where page.keyboard.press() is the better API. The test starts from one focused element and lets the browser choose the next focusable element. Targeting every element with locator.press('Tab') would focus it before each press and weaken the journey being tested.
import { test, expect } from '@playwright/test';
test('supports forward and reverse checkout navigation', async ({ page }) => {
await page.setContent(`
<main>
<label>Email <input type="email"></label>
<label>Postal code <input inputmode="numeric"></label>
<button>Review order</button>
</main>
`);
const email = page.getByLabel('Email');
const postalCode = page.getByLabel('Postal code');
const review = page.getByRole('button', { name: 'Review order' });
await email.focus();
await page.keyboard.press('Tab');
await expect(postalCode).toBeFocused();
await page.keyboard.press('Tab');
await expect(review).toBeFocused();
await page.keyboard.press('Shift+Tab');
await expect(postalCode).toBeFocused();
});
Test critical sequences rather than duplicating the entire page's DOM order. A full-page Tab snapshot becomes costly when legitimate links or controls are added. Focus on modal traps, checkout progression, composite widgets, skip links, and known regression points.
If focus skips a disabled control, that may be correct. If it enters hidden content, lands behind a modal, or becomes invisible, treat it as a product defect rather than forcing the next locator.
Use toBeFocused() instead of querying document.activeElement for normal assertions. The matcher is readable, supports locator retry behavior, and gives better failure context. For broader keyboard accessibility locator patterns, the Playwright getByRole examples provide complementary techniques.
5. Exercise Cross-Platform Command Shortcuts
ControlOrMeta represents the primary command modifier. It becomes Control on Windows and Linux and Meta on macOS. Use it for product shortcuts that intentionally follow native platform conventions.
import { test, expect } from '@playwright/test';
test('opens and closes a command palette', async ({ page }) => {
await page.setContent(`
<main tabindex="-1">
<h1>Workspace</h1>
</main>
<section role="dialog" aria-label="Command palette" hidden>
<label>Command <input></label>
</section>
<script>
const main = document.querySelector("main");
const dialog = document.querySelector("[role=dialog]");
const input = dialog.querySelector("input");
document.addEventListener("keydown", event => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
dialog.hidden = false;
input.focus();
}
if (event.key === "Escape" && !dialog.hidden) {
dialog.hidden = true;
main.focus();
}
});
</script>
`);
const main = page.getByRole('main');
await main.focus();
await page.keyboard.press('ControlOrMeta+K');
const palette = page.getByRole('dialog', { name: 'Command palette' });
await expect(palette).toBeVisible();
await expect(palette.getByLabel('Command')).toBeFocused();
await page.keyboard.press('Escape');
await expect(palette).toBeHidden();
await expect(main).toBeFocused();
});
The shortcut applies at document level, so the page keyboard is appropriate. The test also checks the full lifecycle: opening, moving focus, closing, and restoring focus.
Do not use ControlOrMeta when the product literally specifies Ctrl on every operating system. Test the advertised behavior. Also avoid browser-reserved shortcuts whose effect occurs outside the page. Playwright can reliably test events and outcomes owned by the web application, not arbitrary operating-system chrome.
If a shortcut should be ignored while a user types in a text field, add that negative case. Focus the field, press the shortcut, and assert the palette remains hidden. That protects users from global commands that steal common editing keystrokes.
6. Navigate a Listbox with Arrow Keys
Custom comboboxes and listboxes need explicit keyboard behavior. The strongest test checks semantic selection state before confirming the final choice.
import { test, expect } from '@playwright/test';
test('selects an environment from a listbox', async ({ page }) => {
await page.setContent(`
<label for="environment">Environment</label>
<input id="environment" role="combobox"
aria-controls="options" aria-expanded="true">
<ul id="options" role="listbox">
<li role="option" aria-selected="true">Development</li>
<li role="option" aria-selected="false">Staging</li>
<li role="option" aria-selected="false">Production</li>
</ul>
<script>
const input = document.querySelector("#environment");
const options = [...document.querySelectorAll("[role=option]")];
let active = 0;
function select(position) {
active = Math.max(0, Math.min(position, options.length - 1));
options.forEach((option, index) => {
option.setAttribute("aria-selected", String(index === active));
});
}
input.addEventListener("keydown", event => {
if (event.key === "ArrowDown") select(active + 1);
if (event.key === "ArrowUp") select(active - 1);
if (event.key === "Enter") input.value = options[active].textContent;
});
</script>
`);
const environment = page.getByRole('combobox', { name: 'Environment' });
await environment.press('ArrowDown');
await expect(page.getByRole('option', { selected: true }))
.toHaveText('Staging');
await environment.press('Enter');
await expect(environment).toHaveValue('Staging');
});
Avoid selecting the second li with CSS and claiming keyboard coverage. The test should drive the same key contract as a user. It should also assert accessible state such as selected, expanded, or active descendant when the component uses it.
Boundary tests are valuable. Press ArrowUp at the first item and verify the component either stays there or wraps, according to the specification. Press Escape and verify the list closes without committing a new value. These cases catch state machine defects that a simple click never reaches.
Keep fixture logic simple when demonstrating Playwright. In a real application test, the product supplies the script and the test contains only interaction and assertions.
7. Test Selection, Replacement, and Editor Behavior
Text editors often intercept shortcuts, maintain selection models, and react to individual events. Pick the narrowest Playwright input API that represents the behavior.
import { test, expect } from '@playwright/test';
test('replaces an editor value through select all', async ({ page }) => {
await page.setContent(`
<label for="summary">Summary</label>
<textarea id="summary">Outdated release summary</textarea>
`);
const summary = page.getByLabel('Summary');
await summary.press('ControlOrMeta+A');
await summary.pressSequentially('Verified release summary');
await expect(summary).toHaveValue('Verified release summary');
});
This example deliberately tests select-all. If the requirement were simply to set the value, fill() would be better.
For a held selection, separate key boundaries:
await editor.focus();
await editor.press('End');
await page.keyboard.down('Shift');
try {
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
} finally {
await page.keyboard.up('Shift');
}
await page.keyboard.press('Backspace');
The finally block prevents Shift from leaking into later actions if an intermediate operation fails. This matters in reusable component tests and long editor workflows.
keyboard.insertText() dispatches text input without keydown and keyup, which can be useful for characters not represented by a US keyboard. It is not appropriate when shortcut or key event logic is the subject. pressSequentially() is appropriate when suggestion, formatting, or validation code reacts to each character.
Prefer output assertions over internal event arrays. Event-order assertions belong mainly in low-level component suites where the event contract itself is the public behavior.
8. Send Keys to Controls Inside Frames
A frame has its own document and focus context. Target the element through a FrameLocator and call press() on the resulting locator. This is clearer than hoping a page-level key reaches whichever embedded document currently owns focus.
import { test, expect } from '@playwright/test';
test('submits a framed search field', async ({ page }) => {
await page.setContent(`
<iframe title="Search widget"
srcdoc="<label>Query <input></label><p role='status'></p>
<script>
const input = document.querySelector('input');
input.addEventListener('keydown', event => {
if (event.key === 'Enter') {
document.querySelector('[role=status]').textContent =
'Submitted ' + input.value;
}
});
</script>">
</iframe>
`);
const widget = page.frameLocator('iframe[title="Search widget"]');
const query = widget.getByLabel('Query');
await query.fill('frame locators');
await query.press('Enter');
await expect(widget.getByRole('status'))
.toHaveText('Submitted frame locators');
});
The example keeps lookup, input, and assertion in the same frame. A locator used in filter({ has }) cannot cross frames, but direct frame locators are the right abstraction for interactions within embedded content.
When an iframe navigates or rerenders, locators remain preferable to cached element handles because they resolve against current state. Assert a frame-owned outcome after the key. A top-page assertion can miss a failure hidden inside the widget.
If the frame is cross-origin, Playwright can still interact through its frame APIs when browser security and test setup permit it. Avoid reaching into the frame with application JavaScript. The locator approach models browser automation and produces better traces.
9. Verify Event Semantics in a Component Test
Most end-to-end tests should assert user-visible state. A design-system or editor component may also need a focused event contract test. Keep it narrow and explicit.
import { test, expect } from '@playwright/test';
test('emits modifier state for a save shortcut', async ({ page }) => {
await page.setContent(`
<label for="editor">Editor</label>
<textarea id="editor"></textarea>
<output id="event-log"></output>
<script>
const editor = document.querySelector("#editor");
editor.addEventListener("keydown", event => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") {
event.preventDefault();
document.querySelector("#event-log").textContent =
JSON.stringify({
key: event.key.toLowerCase(),
primaryModifier: event.ctrlKey || event.metaKey
});
}
});
</script>
`);
await page.getByLabel('Editor').press('ControlOrMeta+S');
await expect(page.locator('#event-log')).toHaveText(
'{"key":"s","primaryModifier":true}'
);
});
This is a valid event-level assertion because the component's shortcut contract is the subject. A feature-level save test should go further and verify saved content, confirmation state, or persisted data.
The delay option controls time between keydown and keyup:
await page.getByLabel('Editor').press('Enter', { delay: 75 });
Do not add it by habit. Timing-based behavior should have an explicit requirement. Synchronization after the event belongs in a retrying assertion, not inside an arbitrary press duration.
When debugging, log event.key, modifier booleans, target identity, and active element. Remove broad listeners once the issue is understood because they can change application timing and clutter reports.
10. Scale playwright keyboard press examples with Data
A small table of scenarios can remove repetition when the setup and assertion shape are genuinely identical. Preserve readable test titles and expected outcomes.
import { test, expect } from '@playwright/test';
const cases = [
{ key: 'Enter', expected: 'accepted' },
{ key: 'Escape', expected: 'cancelled' },
] as const;
for (const scenario of cases) {
test('handles ' + scenario.key, async ({ page }) => {
await page.setContent(`
<button id="target">Action</button>
<p role="status">waiting</p>
<script>
const target = document.querySelector("#target");
target.addEventListener("keydown", event => {
if (event.key === "Enter") {
document.querySelector("[role=status]").textContent = "accepted";
}
if (event.key === "Escape") {
document.querySelector("[role=status]").textContent = "cancelled";
}
});
</script>
`);
await page.getByRole('button', { name: 'Action' })
.press(scenario.key);
await expect(page.getByRole('status'))
.toHaveText(scenario.expected);
});
}
Do not force unlike workflows into one data table. Enter navigation, Escape dismissal, and Tab traversal often require different setup and assertions. Duplication is cheaper than a generic helper with flags that conceal behavior.
Page objects should expose domain commands, not thin aliases for Playwright. openCommandPalette() can own the global shortcut and visibility assertion. pressKey(key) adds little value because it hides the recipient without expressing a business outcome.
Use typed literal data so unsupported or misspelled scenario values are caught where possible. Keep expected output next to each key, and let Playwright's test title identify the failed case. The typed Playwright fixtures guide shows how to provide reusable page objects without global mutable state.
11. Make Keyboard Examples Stable in CI
CI failures usually reveal missing focus assumptions, platform coupling, or weak synchronization. Start by capturing a trace on retry and inspecting active state around the press.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
trace: 'on-first-retry',
},
});
Apply these review checks:
- The locator identifies one intended recipient through role, label, or another stable contract.
- Page-level input has an explicit focus prerequisite.
- The key string uses Playwright syntax and intentional platform behavior.
- The assertion proves the feature handled the key.
- Async results use web-first assertions, event promises, or URL checks.
- Held modifiers are released through
finally. - The scenario does not depend on browser chrome or an operating-system dialog.
- No fixed sleep masks a state transition.
- Tests begin with isolated state and do not share an open menu or editor.
- Responsive duplicates cannot silently receive the key.
Run a focused test in debug mode when necessary:
npx playwright test tests/keyboard-examples.spec.ts --debug
A timeout is evidence, not a reason to increase every budget. Determine whether the action waited for an element, the key went to the wrong focus, or the outcome never appeared. For systematic timeout analysis, see how to fix Playwright timeout exceeded.
Interview Questions and Answers
Q: Why is locator.press() usually preferred over page.keyboard.press()?
The locator form resolves and focuses a known element before dispatching the key. That reduces ambiguity and produces clearer traces. I use the page keyboard when current focus or a document-level shortcut is the behavior being tested.
Q: How would you test Enter submission without creating a false positive?
I fill a valid field, press Enter on that field, and assert the business outcome such as navigation, a status message, or rendered results. I do not use the unchanged input value as proof because it was established before the key.
Q: What is the correct way to test Tab order?
I focus a deterministic starting element, call page.keyboard.press('Tab'), and assert the next expected locator with toBeFocused(). I test critical focus paths rather than mirroring every element on the page.
Q: How do you keep keyboard shortcuts portable?
I use ControlOrMeta when the product follows the platform's primary command convention. I verify that the page owns the shortcut and avoid browser-reserved behavior. When the product deliberately differs by platform, I make that branching explicit in test data.
Q: When is an event-level assertion appropriate?
It is appropriate in a component or design-system test where key, modifier, or event ordering is the public contract. In an end-to-end feature test, I favor the resulting UI or persisted behavior because correct events do not guarantee a correct feature.
Q: How do you prevent held modifier state from leaking?
I pair every keyboard.down() with keyboard.up() and put the release in a finally block. I also keep held-key helpers small and use isolated pages per test.
Q: What makes a keyboard test flaky in CI?
Typical causes are implicit focus, overlays, responsive duplicates, hard-coded platform modifiers, asynchronous results without proper assertions, and leaked modifier state. A trace usually shows which element was active and what state existed at the failure.
Common Mistakes
- Proving only the setup value after pressing Enter, so the test can pass when submission is broken.
- Calling the page keyboard after a long workflow without verifying active focus.
- Repeatedly using locator-scoped Tab and accidentally resetting the focus journey.
- Assuming Escape only hides a surface while ignoring required focus restoration.
- Hard-coding Control for a shortcut intended to follow macOS conventions.
- Treating
delayas a replacement for waiting on an application result. - Using
pressSequentially()for every field and slowing the suite without a per-key requirement. - Forgetting to release a manually held modifier after an assertion failure.
- Testing browser chrome or native dialogs that the page does not own.
- Selecting listbox items directly with CSS while claiming arrow-key coverage.
- Combining unrelated keyboard workflows into an unreadable data-driven helper.
- Using broad event listeners for permanent assertions when a visible outcome is the real contract.
A strong review asks whether the test would fail if the application ignored the key, whether the intended element truly receives focus, and whether the expected behavior works without a pointer. Those questions find more risk than simply checking for a call to press().
Conclusion
These playwright keyboard press examples share one design rule: establish who receives the key, send the smallest meaningful sequence, and assert the user-visible consequence. Locator-scoped presses suit known controls, while the page keyboard suits global commands and natural focus traversal.
Choose recipes based on behavior, not convenience. Add Enter, Escape, Tab, arrow, and shortcut coverage where keyboard access matters to the product, keep modifiers portable and bounded, and use traces plus semantic assertions to make failures actionable.
Interview Questions and Answers
Why is locator.press() usually preferred for a field?
It resolves and focuses the intended field before sending the key sequence. That makes ownership explicit and reduces failures caused by stale focus. I use page.keyboard.press() when focus movement itself is under test.
How do you test Enter submission correctly?
I enter valid data, press Enter on the relevant field, and assert a business outcome such as URL, status, results, or saved state. I avoid asserting only the input value because it does not prove the submit handler ran.
How do you test a modal Escape contract?
I first verify focus is inside the modal, press Escape, assert the modal is hidden, and assert focus returns to the opener. This covers both dismissal and keyboard continuity.
Why is page.keyboard.press() appropriate for Tab order?
It sends Tab from the current focus and lets the browser determine the next target. Focusing a locator before every Tab would alter the journey and could hide a broken natural order.
How do you test an arrow-driven listbox?
I press arrows on the combobox, assert semantic selected or active state, then press Enter and assert the committed value. Directly selecting a DOM node would not cover keyboard behavior.
How do you prevent platform-specific shortcut failures?
I use ControlOrMeta for native primary-modifier behavior and test explicit modifiers when the specification is fixed. I also avoid relying on browser chrome or operating-system dialogs.
What is your CI debugging process for keyboard tests?
I inspect the trace, confirm the active element, check overlays and responsive duplicates, validate the key string, and look for leaked modifiers. Then I replace timing guesses with assertions on the actual result.
Frequently Asked Questions
What is a simple Playwright Enter key example?
Locate the field and call await field.press('Enter'), then assert submission, navigation, or rendered output. Do not assert only the field value because that value existed before Enter.
How do I test Escape in Playwright?
Press Escape on the focused control or through page.keyboard when current focus is intentional. Assert that the dialog or menu closes and that focus returns to the expected element when restoration is part of the contract.
How do I test Tab and Shift+Tab?
Focus a known starting element, use page.keyboard.press('Tab') or page.keyboard.press('Shift+Tab'), and assert the next element with toBeFocused(). This preserves natural browser focus traversal.
Can Playwright test keyboard shortcuts on macOS and Windows?
Yes. Use ControlOrMeta for shortcuts based on the operating system's primary command modifier. Use explicit Control or Meta when the application specification requires that exact key.
How do I press arrow keys in a Playwright listbox?
Call press('ArrowDown') or press('ArrowUp') on the combobox or focused widget. Assert semantic state such as the selected option before pressing Enter to commit the choice.
How do I press keys inside an iframe?
Create a FrameLocator, locate the control inside it, and call press() on that locator. Keep the resulting assertions in the same frame so the recipient and outcome remain unambiguous.
Why should keyboard tests avoid waitForTimeout?
A fixed sleep guesses when asynchronous work will finish and can remain flaky. Web-first assertions wait only until the required state appears and produce better failure evidence.
Related Guides
- Playwright drag and drop: Examples and Best Practices
- Playwright mock date and time: Examples and Best Practices
- Playwright popup and new tab handling: Examples and Best Practices
- Playwright tag and grep filters: Examples and Best Practices
- Playwright APIRequestContext: Examples and Best Practices
- Playwright aria snapshot: Examples and Best Practices