fix(tools): capture the Xtream guide shot against the current connection test (#1807)

This commit is contained in:
4gray authored and GitHub committed 2026-10-04 11:53:17 +02:00
1 parent ef795e56bc
commit 7fd3d1dab0
5 files changed
+38 -17

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 407 KiB

@@ -66,18 +66,21 @@ credentials. IPTVnator can read them out of the link for you, as shown below.
'Open the Add playlist dialog. You will find the button on the dashboard, in the header playlist switcher, and in the command palette under "Add Xtream Codes playlist".',
'Choose the "Xtream credentials" method.',
'Fill in a playlist title of your choice, the server URL, the username and the password.',
'Click "Test Connection". IPTVnator asks the portal for the account status and shows the result under the form.',
'Click "Add". IPTVnator downloads the categories for live TV, movies and series and opens the new source.',
'Click "Test HTTPS and HTTP". IPTVnator asks the portal for the account status and shows the result under the server URL. If an `https://` address refuses the connection or does not speak HTTPS, the test retries the same address over plain HTTP once, which is why the dialog warns that your username and password may travel unencrypted.',
'Click "Add playlist". IPTVnator downloads the categories for live TV, movies and series and opens the new source.',
]}
/>
![Add playlist dialog with the Xtream credentials method selected and a successful connection test](/iptvnator/blog/guides/screenshots/guide-xtream-add-playlist-dark.png)
The connection test reports one of four states. **Connection successful! Portal is active** is
the one you want. **Portal subscription has expired** and **Portal is inactive** mean the
portal answered but refuses the account; check the subscription with your provider. **Could
not connect to the portal** means no answer at all: a typo in the server URL, a wrong port, a
missing `http://` or `https://`, or a host that is down.
**Connected. The portal is active.** is the result you want; **Connected using HTTP** means the
same over an unencrypted connection, after the HTTPS attempt failed and the server URL was
switched to `http://`. **The subscription has expired** and **The account is inactive or the
credentials are incorrect** mean the portal answered but refuses the account: check the
credentials, then the subscription with your provider. **Could not reach the server** means no
answer at all: a typo in the server URL, a wrong port, or a host that is down. The remaining
messages name what went wrong instead: a failed secure connection, an HTTP error status, or a
server that answered without valid account information.
<Alert type="success" title="Paste the whole link">
If you paste a complete `get.php` or `player_api.php` link into the Server URL field while the
+5 -1
View File
@@ -168,7 +168,11 @@ into `apps/website/public/blog/guides/screenshots/` instead of a release folder
guard a release shot does; the add-playlist dialog shots fill the form with the
mock's fictional `marketing` credentials and use a labeled hand-out for the
Auto-detect method rather than a `get.php?username=…` link, because G4 rejects
any URL carrying query credentials. Shots that walk into a Stalker portal
any URL carrying query credentials. The Xtream shot clicks **Test HTTPS and
HTTP** against the plain-`http://` mock, so no HTTPS probe is made, and fails
the run unless the status line reports an active portal; the mock does not
check passwords, so that verdict proves the scenario answered, not that the
password is right. Shots that walk into a Stalker portal
(`open-stalker-live`) make the run start the stalker-mock-server on port 3210
and seed its `marketing-demo` portal as a third source, which is why they are
never part of a release run. That scenario's MAC, `00:1A:79:00:00:07`, is the
@@ -63,17 +63,31 @@ async function openAddPlaylistXtream(page: Page): Promise<void> {
await dialog.locator('#serverUrl').fill(XTREAM_MOCK_ORIGIN);
await dialog.locator('#username').fill(XTREAM_FIXTURE_CREDENTIALS.username);
await dialog.locator('#password').fill(XTREAM_FIXTURE_CREDENTIALS.password);
// The status probe only talks to the local mock, so the frame can
// show the successful "portal is active" verdict the guide explains.
// The server URL is plain http://, so the probe never tries HTTPS and
// only talks to the local mock; the frame can show the successful
// "portal is active" verdict the guide explains.
await dialog
.getByRole('button', { name: /test connection/i })
.first()
.getByRole('button', { name: 'Test HTTPS and HTTP', exact: true })
.click();
const status = dialog.locator('.connection-status');
await status.waitFor({ state: 'visible', timeout: 30_000 });
// The dialog body scrolls; bring the verdict the guide explains
// into frame together with the credential fields above it.
await status.scrollIntoViewIfNeeded();
// The status line first reads "Testing connection…"; wait for the
// verdict itself, so a refused account fails the shot instead of
// publishing a frame that contradicts the guide.
const status = dialog.getByRole('status');
await status
.filter({ hasText: /portal is active/i })
.waitFor({ state: 'visible', timeout: 30_000 })
.catch(async () => {
const shown = await status
.textContent({ timeout: 1_000 })
.catch(() => null);
throw new Error(
`Xtream connection test did not report an active portal within 30s (status: ${shown?.trim() || 'none'})`
);
});
// The dialog body scrolls. The verdict sits under the server URL,
// above the credentials, so scrolling to the last field frames the
// whole filled form together with the verdict the guide explains.
await dialog.locator('#password').scrollIntoViewIfNeeded();
await page.waitForTimeout(500);
}