Agentic Browser Testing
petrkindlmann/qa-skills
Goal-driven E2E testing where a browser agent (Playwright MCP / computer-use) reads a natural-language goal and explores the app via the accessibility tree to assert outcomes — no pre-written script.
A skill your agent uses when writing Playwright E2E tests for critical user journeys, setting up post-deployment smoke tests, debugging flaky browser automation, or implementing BDD feature files…
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install kid-sid/claude-spellbook solution-testing --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/solution-testing .claude/skills/solution-testing && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .claude/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testingType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install kid-sid/claude-spellbook solution-testing --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/solution-testing .agents/skills/solution-testing && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .agents/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install kid-sid/claude-spellbook solution-testing --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/solution-testing .cursor/skills/solution-testing && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .cursor/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/kid-sid/claude-spellbook.git --path skills/solution-testing--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install kid-sid/claude-spellbook solution-testing --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/solution-testing .gemini/skills/solution-testing && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .gemini/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install kid-sid/claude-spellbook solution-testingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/solution-testing .github/skills/solution-testing && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .github/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kid-sid/claude-spellbook --skill solution-testing -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install kid-sid/claude-spellbook solution-testing --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/solution-testing .opencode/skills/solution-testing && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "solution-testing" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/solution-testing into .opencode/skills/solution-testing/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "solution-testing", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
solution-testingA skill your agent uses when writing Playwright E2E tests for critical user journeys, setting up post-deployment smoke tests, debugging flaky browser automation, or implementing BDD feature files…
Solution Testing is an agent skill from kid-sid/claude-spellbook. Use when writing Playwright E2E tests for critical user journeys, setting up post-deployment smoke tests, debugging flaky browser automation, or implementing BDD feature files with Gherkin.
Its SKILL.md is about 3.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering End-to-end testing, Customer journey mapping and Browser testing. It works with Playwright. The repository describes itself as: A curated collection of skills, prompts, and workflows that extend Claude's capabilities — your personal grimoire for AI-powered development. The licence is MIT.
Read from SKILL.md and the folder at commit a7c2ac9. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
SMOKE_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Solution Testing loads about 3.9k tokens when it runs. Until then it costs about 52 tokens; SKILL.md has 1,101 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from kid-sid/claude-spellbook at commit a7c2ac9, republished under its MIT licence (© kid-sid). 1,101 words, ~3,911 tokens.
.claude/skills/solution-testing/SKILL.md (or your agent's skills folder).End-to-end and acceptance testing techniques for verifying that a feature works correctly across the full stack — browser, API, and data layer — from the user's perspective.
E2E tests cover things integration tests cannot:
| Type | Speed | Flakiness Risk | Maintenance Cost |
|---|---|---|---|
| Unit | ms | Very low | Low |
| Integration | seconds | Low | Medium |
| E2E | 10s–minutes | High | High |
Prefer more service-level integration tests over E2E tests. E2E tests are expensive to write, slow to run, and prone to flakiness. Use them sparingly.
npm init playwright@latest
# or add to an existing project:
npm install -D @playwright/test
npx playwright installConfig (playwright.config.ts):
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
reporter: [['html'], ['list']],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});Each page or major component has a class that encapsulates its selectors and actions. Test files use POM methods — never raw locators.
// pages/login.page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
private readonly emailInput: Locator;
private readonly passwordInput: Locator;
private readonly submitButton: Locator;
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test('user can log in with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});| Locator | Example | Why preferred / when to use |
|---|---|---|
getByRole | getByRole('button', { name: 'Submit' }) | Accessibility-based, most stable, mirrors how users perceive UI |
getByLabel | getByLabel('Email address') | Form inputs — semantically tied to label text |
getByText | getByText('Welcome back') | Unique visible text content |
getByTestId | getByTestId('submit-btn') | When no semantic selector works; use data-testid attribute |
| CSS selector | locator('.btn-primary') | Last resort — fragile, breaks on markup changes, avoid |
// BAD
page.locator('#root > div > form > button:nth-child(2)')
// Fragile CSS path — breaks on any DOM restructure
// GOOD
page.getByRole('button', { name: 'Submit' })
// Semantic, resilient, matches accessibility treeNever use hardcoded sleeps. Always wait for an observable UI state.
// BAD
await page.click('#submit');
await page.waitForTimeout(2000); // never do this — hides real timing issues
// GOOD
await page.click('#submit');
await expect(page.getByText('Payment confirmed')).toBeVisible();
// or wait for navigation:
await page.waitForURL('/confirmation');Test complete API workflows over the network — not just service-level unit behavior. This verifies the full auth lifecycle, serialization, and routing.
Key patterns:
request fixture for co-located API and browser teststest('create and retrieve payment', async ({ request }) => {
// authenticate
const authRes = await request.post('/api/auth/token', {
data: { email: 'test@example.com', password: 'password' }
});
const { access_token } = await authRes.json();
// create resource
const createRes = await request.post('/api/payments', {
headers: { Authorization: `Bearer ${access_token}` },
data: { amount: 100, currency: 'USD' }
});
expect(createRes.ok()).toBeTruthy();
const { id } = await createRes.json();
// retrieve and verify
const getRes = await request.get(`/api/payments/${id}`, {
headers: { Authorization: `Bearer ${access_token}` }
});
const payment = await getRes.json();
expect(payment.amount).toBe(100);
});Use BDD when:
Do not use BDD when:
Feature: User Authentication
As a registered user
I want to log in with my credentials
So that I can access my account
Background:
Given a user exists with email "user@example.com"
Scenario: Successful login
When I submit valid credentials for "user@example.com"
Then I should be redirected to the dashboard
And I should see a welcome message
Scenario: Failed login - wrong password
When I submit the wrong password for "user@example.com"
Then I should see "Invalid credentials"
And I should remain on the login page
Scenario Outline: Login with various invalid inputs
When I submit email "<email>" and password "<password>"
Then I should see error "<error>"
Examples:
| email | password | error |
| invalid-email | pass123 | Invalid email format |
| | pass123 | Email is required |
| user@example.com | | Password is required || Language | Tool |
|---|---|
| Node.js | @cucumber/cucumber |
| Python | behave |
| Go | godog |
| Java | Cucumber-JVM |
Use tags to filter test runs: @smoke, @regression, @wip.
# Run only smoke-tagged scenarios
npx cucumber-js --tags @smoke
# Skip work-in-progress scenarios
npx cucumber-js --tags "not @wip"Smoke tests answer one question: "Is the deployed system alive?" They are not comprehensive — they verify only the critical path. If a smoke test fails, the deployment must be rolled back or halted immediately.
Run smoke tests automatically after every deployment to staging and production.
Criteria for inclusion: if this breaks, the system is unusable for most users.
test.describe('Smoke', () => {
test('health endpoint returns 200', async ({ request }) => {
const res = await request.get('/health');
expect(res.status()).toBe(200);
});
test('home page loads', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
test('user can log in', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(process.env.SMOKE_USER!, process.env.SMOKE_PASSWORD!);
await expect(page).toHaveURL('/dashboard');
});
});Run with:
npx playwright test --grep @smokeTag smoke tests with @smoke in Playwright using test.describe metadata or a custom tag fixture so they can be selected independently from the full suite.
| Cause | Fix |
|---|---|
Hardcoded waitForTimeout | Replace with observable state assertions (toBeVisible, etc.) |
| Shared test data across parallel tests | Use unique IDs per test run (e.g., Date.now() suffix) |
| Tests depend on execution order | Each test must set up its own state in beforeEach |
| Timezone or locale sensitivity | Fix locale in test environment config |
| Race conditions in UI during animation | Use toBeVisible() / toBeEnabled() — not isVisible() |
| Network variability in CI | Increase timeouts in CI config, not with waitForTimeout |
When a test is flaky and cannot be fixed immediately, quarantine it rather than deleting it. Deletion loses coverage history; quarantine preserves intent and tracks remediation.
test.fixme('payment flow — FLAKY: race condition in payment widget', async ({ page }) => {
// tracked in: https://github.com/org/repo/issues/123
// do not delete — re-enable once widget stabilised
});test.fixme skips the test and marks it as expected to fail. Remove the .fixme once the underlying issue is resolved.
Never use production accounts or shared test users in E2E tests. Shared state causes interference between parallel runs and makes failures non-deterministic.
let testUser: { id: string; email: string };
test.beforeEach(async ({ request }) => {
// create an isolated test user for this test run
const res = await request.post('/api/test/users', {
data: { email: `test-${Date.now()}@example.com` }
});
testUser = await res.json();
});
test.afterEach(async ({ request }) => {
// clean up — do not leave test data in the database
await request.delete(`/api/test/users/${testUser.id}`);
});/api/test/*) must only be available in test and staging environmentsNODE_ENV check in the server — never expose in production// server-side guard (Express example)
if (process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'staging') {
throw new Error('Test helpers only available in test/staging environments');
}# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
CI: true
BASE_URL: http://localhost:3000
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/Key CI settings:
CI=true so Playwright applies retries: 2 from configplaywright-report/ as artifact on failure for post-mortem debugginggetByRole, getByLabel, or data-testid attributes that survive UI changeswaitForTimeout as an explicit sleep — arbitrary sleeps make tests slow and flaky; always wait on observable state (waitForSelector, expect(locator).toBeVisible())test.fixme and a tracking issue, fix within the same sprintgetByRole / getByLabel — no fragile CSS selectorswaitForTimeout — all waits are based on observable statetest.fixme and a tracking issue, not deletedbeforeEach and cleaned up in afterEachretries: 2 in CI config)playwright.config.ts)© kid-sid, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/solution-testing of kid-sid/claude-spellbook.
Open the folder on GitHubat commit a7c2ac9
Solution Testing next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Solution Testing this skillkid-sid/claude-spellbook | 189 | — | ~3.9k | Automated safety check: Pass | MIT | |
| Agentic Browser Testingpetrkindlmann/qa-skills | 165 | — | ~4.5k | Automated safety check: Pass | MIT | |
| playwright-cli Browser Automationgithub/gh-aw | 5.4k | 24 repos | ~2.8k | Automated safety check: Pass | MIT | |
| Glance TestDebugBase/glance | 156 | — | ~827 | Automated safety check: Pass | MIT | |
| Open BrowserJasonHonKL/Openbrowser | 114 | — | ~1.6k | Automated safety check: Pass | MIT | |
| Playwright Testingchongdashu/vibejam-starter-pack | 149 | — | ~2.2k | Automated safety check: Pass | None |
petrkindlmann/qa-skills
Goal-driven E2E testing where a browser agent (Playwright MCP / computer-use) reads a natural-language goal and explores the app via the accessibility tree to assert outcomes — no pre-written script.
github/gh-aw
Drives a real browser from the command line with playwright-cli to open pages, interact, mock requests, save state and work with Playwright tests.
DebugBase/glance
Run E2E browser tests on any web application using Glance MCP.
JasonHonKL/Openbrowser
A skill your agent uses whenever the task involves browsing web pages, extracting page content, clicking forms, or completing web workflows.
chongdashu/vibejam-starter-pack
Plan, implement, and debug frontend tests: unit/integration/E2E/visual/a11y.
cin12211/orca-q
Playwright E2E testing expert for browser automation, cross-browser testing, visual regression, network interception, and CI integration.
kid-sid/claude-spellbook
A skill your agent uses when building or reviewing UI components for keyboard and screen reader compatibility, adding ARIA to custom widgets, auditing a page for WCAG AA conformance, or preparing…
kid-sid/claude-spellbook
A skill your agent uses when building, wiring, or debugging an Agentex agent — choosing agent type, configuring acp.py and manifest.yaml, using adk.messages or adk.state, or resolving…
kid-sid/claude-spellbook
A skill your agent uses when building production LLM applications — designing RAG pipelines, choosing vector databases, implementing agent orchestration, optimizing cost, or adding AI safety…
kid-sid/claude-spellbook
A skill your agent uses when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change…
kid-sid/claude-spellbook
A skill your agent uses when designing new REST endpoints, reviewing an existing API contract, adding pagination or filtering, planning a versioning strategy, or building a public or partner-facing…
kid-sid/claude-spellbook
A skill your agent uses when implementing login flows, issuing or validating JWTs, setting up OAuth2/OIDC with a provider, designing role-based or attribute-based access control, securing API…
Works with
Categories
A skill your agent uses when writing Playwright E2E tests for critical user journeys, setting up post-deployment smoke tests, debugging flaky browser automation, or implementing BDD feature files…. Solution Testing is an agent skill from kid-sid/claude-spellbook. Use when writing Playwright E2E tests for critical user journeys, setting up post-deployment smoke tests, debugging flaky browser automation, or implementing BDD feature files with Gherkin.
Solution Testing fits situations like: writing Playwright E2E tests for critical user journeys; setting up post-deployment smoke tests; debugging flaky browser automation; implementing BDD feature files with Gherkin.
Run `npx skills add kid-sid/claude-spellbook --skill solution-testing -a claude-code`. Or copy the skill folder (skills/solution-testing in kid-sid/claude-spellbook) into .claude/skills/solution-testing in your project. Claude Code loads it when a task matches its description.
Run `npx skills add kid-sid/claude-spellbook --skill solution-testing -a codex`. Or copy the skill folder (skills/solution-testing in kid-sid/claude-spellbook) into .agents/skills/solution-testing in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add kid-sid/claude-spellbook --skill solution-testing -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/solution-testing, .gemini/skills/solution-testing, .github/skills/solution-testing and .opencode/skills/solution-testing in your project.
Going by SKILL.md and its folder, Solution Testing needs the command-line tools its instructions call (npx and npm) and credentials named SMOKE_PASSWORD. Our summary lists: Python 3; Node.js.
SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Solution Testing is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 3.9k tokens (SKILL.md is roughly 16k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Solution Testing: Agentic Browser Testing (petrkindlmann/qa-skills, 165 stars), playwright-cli Browser Automation (github/gh-aw, 5.4k stars), Glance Test (DebugBase/glance, 156 stars) and Open Browser (JasonHonKL/Openbrowser, 114 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
kid-sid (a GitHub user) maintains it in kid-sid/claude-spellbook, which has 189 GitHub stars. The repository holds 52 skills in this directory. The repository was last updated on August 5, 2026.
Source: kid-sid/claude-spellbook on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.