# Changelog of Ultimate Screenshot (`dz_omar/ultimate-screenshot`) Actor

- **URL**: https://apify.com/dz\_omar/ultimate-screenshot/changelog.md
- **Full Actor documentation**: https://apify.com/dz\_omar/ultimate-screenshot.md

## Changelog

### 2.0.0

A rebuild around one idea: every setting should describe something the actor
actually does, and nothing a run does should be able to grow without a limit.

#### Video and GIF are honest now

- **Recording length and frame rate replace the old frame controls.** A clip is
  now exactly `Recording length × Frames per second` frames and plays for
  exactly the length you asked for. The previous version advertised a frame rate
  it never passed to the encoder — GIFs were fixed at 10 fps and MP4s at 30 —
  and fed frames in at a different rate again, so a "5 second" GIF came out at
  roughly 0.6 seconds.
- **Scroll recordings reach the bottom of the page.** The step between frames is
  worked out from the real page height, so the last frame lands at the end. The
  old fixed pixel step ignored the page, so short pages ended in a run of
  identical footer frames and long pages never got past the first screen.
- **GIFs get a proper colour palette and loop.** They are also scaled to at most
  800 pixels wide: a 5-second 1920-wide GIF measured 10 MB, the same clip at 800
  is under 2.5 MB.
- **Recording is roughly twice as fast**, because frames are captured in a
  format that a browser can produce in a third of the time and both video
  encoders are lossy anyway.

#### Limits that hold

- **Scrolling always stops.** There is a scrolling limit in seconds, it applies
  to every run, and it is what ends the scroll on an endless feed. The old
  "infinite scroll" switch had no stopping condition at all: on a real feed the
  run scrolled until the platform killed it.
- **Every stage has a deadline** — the page, the scroll, the recording, the
  encoder. A stage that runs long is stopped and whatever was captured is still
  delivered, instead of the run hanging.
- **Very tall pages are clipped, deliberately and visibly.** A full-page image
  is capped at 16 000 pixels, which is the tallest a browser can render in one
  pass, and the result row says when clipping happened.

#### Fixes

- `0` retries used to mean *zero attempts*: the page was never opened and the
  screenshot was of a blank tab.
- PDF margins were sent to the browser as bare numbers, which it reads as
  **pixels**. A margin set to 20 mm came out at about 5 mm. Margins now carry
  their unit.
- Device emulation no longer stretches recordings. The old encoder scaled every
  clip to the desktop width setting, so a phone capture was stretched to 1920.
- A page that fails no longer takes the run with it — each page is captured in
  its own isolated browser context, and a browser that stops responding is
  restarted.
- The encoder is no longer invoked through a shell, so a long encode can no
  longer fail after it has already succeeded.
- Removed a loop that slept for one second per frame *after* encoding finished,
  which added a minute of idle time to a 60-frame video.

#### New

- **Wait for an element** before capturing, and **hide elements** (cookie
  banners, chat bubbles) by CSS selector.
- **JPEG quality** and **landscape PDF** controls.
- **Interrupted runs resume** instead of starting over, so a restart does not
  re-capture pages that were already delivered.
- Every result row now carries the page title, the final address, the file size,
  how long the capture took, and — when a page produced nothing — a sentence
  saying what happened.

#### Settings from the previous version

`frameCounT`, `frame`, `timefullPagE` and `infiniteScroll` are still accepted
and are converted to the current settings; `frameIntervaL`, `scrollSteP` and
`outputDir` no longer have an effect. Any conversion is reported in the run log.
Every other setting keeps its name and meaning.

#### Target URLs are validated as addresses

The **Target URLs** field now checks that each line looks like a web address, so a
stray number or word is caught in the form instead of producing an empty result.

The field still takes a plain list of addresses, exactly as before — `["https://example.com"]`
keeps working, and a bare domain such as `example.com` is completed for you. Existing
integrations need no change.

The one behaviour that changes: a list containing an entry that is *not* an address is now
rejected when the run is submitted, naming the position of the bad entry, rather than starting
a run that returns nothing for it.

### 2.0.2

Two settings that did not work at all in 2.0.

- **Cookies now work.** Supplying cookies failed the page outright with an
  internal error, so any capture of a signed-in page returned nothing.
- **"Verify certificates: off" now works.** Turning it off had no effect, so a
  page with an expired or self-signed certificate still failed to load.

Both were the same underlying cause — the actor was calling a newer browser
interface than the browser it ships with provides — and both are now written to
work on either version.

Added a test that drives a real browser and checks every single setting in the
input form actually does something, so a setting can no longer be published
without having been run at least once.

### 2.0.3

#### Cookies now work with a real browser export

A Cookie-Editor or Copy Cookies JSON export is understood exactly as it comes
out of the extension. Three of its conventions were previously dropped, any one
of which can lose the session:

- `expirationDate` was ignored, so every cookie became a session cookie
- `sameSite` values such as `no_restriction` were unrecognised and dropped
- the leading dot on `.example.com` was stripped — that dot is what makes a
  cookie apply to `www.example.com`

The field is now marked **secret**: encrypted at rest, never written to the run
log. Only the number of cookies applied is reported.

Each result row now carries `cookiesSupplied` and `cookiesApplied`, and if the
site clears your session while the page loads, the row says so instead of
quietly returning a signed-out page.

#### The run tells you what it is doing

Long captures used to go silent — a 300-frame recording reported nothing until
it finished, and the encoder said nothing at all, so a working run looked frozen.
Every stage now reports to the log and to the run's status line: opening the
page, page loaded, scrolling, recording *n* of *m* frames with an estimate of
the time left, building the palette, encoding, saving. Progress is reported on a
timer, so something moves every few seconds even on a slow page.

### 2.0.5

#### Files are grouped by what they are

The run's storage now shows three named collections instead of one flat list of
keys: **Screenshots** (PNG, JPEG), **Videos & GIFs** (MP4, GIF) and **PDF
documents**.

GIFs are filed with video rather than with the images. A GIF is a recording of
the page — same frame capture, same length and frame-rate settings as an MP4 —
even though its file type is technically an image.

File names now start with the group they belong to (`screenshot-`, `video-`,
`document-`). If you read files from the dataset's `screenshot_url` field, as
most integrations do, nothing changes.

### 2.0.6

#### "Hide elements before capturing" now catches late banners

Cookie banners are usually injected by a third-party script several seconds
after the page has finished loading — on apify.com, 8 to 10 seconds after. The
old behaviour hid whatever was on the page at one instant, which meant a banner
arriving later was still in the shot.

Hiding is now applied as a style rule the moment the page opens, so anything
matching your selectors stays hidden no matter when it appears — and the page
never shifts around as the banner mounts.

Two smaller fixes: one malformed selector no longer stops the valid ones from
working, and a selector that matches nothing is reported in the row's `notes`
instead of failing quietly.

The README now lists ready-made selectors for the common consent platforms.

### 2.0.8

Added a walkthrough screenshot to the README showing how to find a selector for
**Hide elements before capturing**: right-click the banner, choose Inspect, and
read the `id` or `class` off the highlighted element — `#` for an id, `.` for a
class.

### 2.0.9

#### Fixed: blank screenshots of pages that render after loading

Sites that draw their content with JavaScript after the page has technically
finished loading — Facebook's Ad Library, and single-page apps generally — could
come back as a flat sheet of the site's background colour.

The capture now checks whether the page has actually drawn anything. If it has
not, it waits for the content to arrive and settle before shooting, and the
result row explains that it did. Pages that are already drawn are captured
immediately as before, so nothing else got slower.

If you hit this on a specific site regularly, setting **When the page counts as
ready** to a Network Idle mode makes the wait the normal path for it.
