diff --git a/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-dark.png b/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-dark.png index 8883662e4..916c16e53 100644 Binary files a/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-dark.png and b/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-dark.png differ diff --git a/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-light.png b/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-light.png index d17c79e59..de6d4919e 100644 Binary files a/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-light.png and b/apps/website/public/blog/guides/screenshots/guide-xtream-add-playlist-light.png differ diff --git a/apps/website/src/content/blog/xtream-codes-setup-guide.mdx b/apps/website/src/content/blog/xtream-codes-setup-guide.mdx index 5016956c6..f0600967f 100644 --- a/apps/website/src/content/blog/xtream-codes-setup-guide.mdx +++ b/apps/website/src/content/blog/xtream-codes-setup-guide.mdx @@ -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. If you paste a complete `get.php` or `player_api.php` link into the Server URL field while the diff --git a/docs/architecture/release-pipeline.md b/docs/architecture/release-pipeline.md index a9177677a..685d74e1b 100644 --- a/docs/architecture/release-pipeline.md +++ b/docs/architecture/release-pipeline.md @@ -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 diff --git a/tools/release/capture-navigation-setup-actions.ts b/tools/release/capture-navigation-setup-actions.ts index fbfbdaa2a..3689f0f49 100644 --- a/tools/release/capture-navigation-setup-actions.ts +++ b/tools/release/capture-navigation-setup-actions.ts @@ -63,17 +63,31 @@ async function openAddPlaylistXtream(page: Page): Promise { 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); }