# Browser
`mite_browser` is mite's HTML-scraping integration, built on `mite_http` and
[BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/). Despite the
name, it does not drive a real browser engine,there is no JavaScript
execution or rendering. It parses HTML responses and gives journeys a
page/form/link API on top of plain HTTP requests, which is enough to script
form submissions and link navigation without hand-rolling HTML parsing in
every journey.
Install: `pip install mite[browser]` (depends on `http`; no functional change
today. Every mite install already includes it; this will matter starting in
mite 3.0).
## Basic usage
```python
from mite_browser import browser_decorator
@browser_decorator()
async def journey(ctx):
async with ctx.transaction("Load homepage"):
page = await ctx.browser.get("https://example.com")
page.assert_element_in(name="h1", text="Welcome")
```
`browser_decorator()` wraps `@mite_http` and installs `ctx.browser`, a
`Browser` instance built on top of `ctx.http`, for the duration of the
journey call. It accepts two optional arguments:
- `separation` passed to `mite.ensure_fixed_separation`, for pacing
requests within the journey.
- `embedded_resources` if `True`, every page fetched also downloads its
embedded resources (images, scripts, stylesheets, iframes) recursively.
This better simulates a real browser's network footprint, at the cost of
extra requests per page.
## The `Browser` object
`ctx.browser` exposes one async method per HTTP verb used for page
navigation (`get`, `post`, `options`, `patch`) each returning a `Page`.
Anything else, use `ctx.browser.request(method, url, ...)` directly. Cookie
helpers (`erase_all_cookies`, `erase_session_cookies`, `get_cookie_list`) are
also available, forwarding to the underlying `ctx.http` session.
## The `Page` object
A `Page` wraps an HTML response and lazily parses it with BeautifulSoup the
first time you touch `.dom`, `.find()`, or `.find_all()`:
```python
page = await ctx.browser.get("https://example.com")
page.find("a", text="Sign in")
page.find_all("div", {"class": "product"})
```
- `assert_element_in(name=..., attrs=..., text=...)` raises
`ElementNotFoundError` if nothing matches; useful as a lightweight
assertion that a page loaded correctly.
- `click_link(text)` finds an `` by its visible text and follows it via
`ctx.browser.get`.
- `get_form(name=None)` / `get_forms()` see Forms, below.
- `xhr_request(method, url, ...)` / `xhr_post(url, ...)` issues a request
with `X-Requested-With: XMLHttpRequest` and a `Referer` set to the current
page's URL, for simulating an AJAX call made from the page.
If `embedded_resources=True` was passed to the decorator, a page's `.scripts`,
`.stylesheets`, `.resources` and `.frames` lists are populated with the
downloaded sub-resources; otherwise they stay empty.
## Forms
```python
form = page.get_form() # first form on the page
form = page.get_form(name="login")
form["username"] = "test"
form["password"] = "hunter2"
result_page = await form.submit()
```
`Form` behaves like a dict of its fields (`select`, `textarea`, and `input`
elements, with radio groups collapsed into a single field by name).
Assigning to a field name sets its value; assigning to a name that doesn't
exist creates one, so you can inject values a form doesn't declare.
`submit()` sends the form using its declared `method` and `action`.
```{important}
File upload fields are parsed but not actually submitted: `Form._serialize`
only encodes the non-file fields, with a `TODO` in source noting file
support depends on the underlying HTTP backend (`acurl`) supporting
multipart uploads, which it does not today. A form with a file input submits
successfully, just without the file.
```
## Metrics
`mite_browser` emits no metrics of its own. It has no registered stats
processor. Every request it makes goes through `ctx.http`, so the metrics
that show up are `mite_http`'s (see {doc}`http`), not anything
browser-specific.
## See also
- {doc}`http` the HTTP integration `mite_browser` is built on