Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@
"githubPagesInstaller": "https://apolloautomation.github.io/WLED-MM-M1/",
"firmware": {
"stable": {
"WLED 16.0.1 (Rev6 only)": "https://apolloautomation.github.io/WLED-M1/manifest.json",
"WLED-MM 14.5.1 (Rev6)": "https://apolloautomation.github.io/WLED-MM-M1/Rev6_14.5.1/manifest.json",
"WLED-MM 14.5.1 (Rev4)": "https://apolloautomation.github.io/WLED-MM-M1/14.5.1/manifest.json",
"WLED 16.0.1 (Rev6 only)": "https://apolloautomation.github.io/WLED-M1/manifest.json"
"WLED-MM 14.5.1 (Rev4)": "https://apolloautomation.github.io/WLED-MM-M1/14.5.1/manifest.json"
}
},
"repos": {
Expand Down
16 changes: 10 additions & 6 deletions docs/m1-wled-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

The M-1 offers two WLED firmwares in the installer, both served from GitHub Pages:

- WLED-MM 14.5.1 (default) from ApolloAutomation/WLED-MM-M1. Two hardware revisions:
- WLED 16.0.1 (Rev6 only, default) from ApolloAutomation/WLED-M1:
- https://apolloautomation.github.io/WLED-M1/manifest.json
- WLED-MM 14.5.1 from ApolloAutomation/WLED-MM-M1. Two hardware revisions:
- Rev6: https://apolloautomation.github.io/WLED-MM-M1/Rev6_14.5.1/manifest.json
- Rev4: https://apolloautomation.github.io/WLED-MM-M1/14.5.1/manifest.json
- WLED 16.0.1 (Rev6 only) from ApolloAutomation/WLED-M1:
- https://apolloautomation.github.io/WLED-M1/manifest.json

Rev6 with WLED-MM 14.5.1 is the default because that is the hardware being sold and the firmware
shipped on it today. WLED 16.0.1 is a single merged full-install image (bootloader, partition
WLED 16.0.1 is the default. It is a single merged full-install image (bootloader, partition
table, otadata, application, and LittleFS factory config) built from ApolloAutomation/WLED-M1.
Its manifest sets new_install_prompt_erase, so esp-web-tools prompts a full erase, which is what
guarantees a factory-fresh 64x64 boot. It is offered for Rev6 only.
guarantees a factory-fresh 64x64 boot. It is offered for Rev6 only, so Rev4 owners must switch
to the WLED-MM 14.5.1 (Rev4) variant.

Because the default variant has no classic installer page of its own, the M-1 page now loads with
the "Classic installer" link hidden and the header GitHub link pointing at WLED-M1; both come
back when a 14.5.1 variant is selected.

Customers already on WLED-MM 14.5.1 can OTA to 16.0.1 with the M-1_ota.bin app-only image
(same partition table preserves settings); the installer only serves the first-flash full image.
Expand Down
57 changes: 33 additions & 24 deletions tests/installer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,24 +214,28 @@ test('release notes ignore an off-allowlist API url and use the safe releases hr
test('release notes follow the selected variant repo (per-variant repos override)', async ({ page }) => {
const d = registry.devices.find((x) => x.repos && x.repos.stable);
test.skip(!d, 'no device with a per-variant repos override');
const overrideVariant = Object.keys(d.repos.stable)[0];
const overrideRepo = d.repos.stable[overrideVariant];
const defaultVariant = Object.keys(d.firmware.stable)[0];
test.skip(overrideVariant === defaultVariant, 'override is on the default variant');
// Works whichever way round the override sits: the default variant may itself be
// the overridden one, so compare the first variant against the first one that
// resolves to a different repo.
const variants = Object.keys(d.firmware.stable);
const repoOf = (v) => d.repos.stable[v] || d.repo;
const startVariant = variants[0];
const otherVariant = variants.find((v) => repoOf(v) !== repoOf(startVariant));
test.skip(!otherVariant, 'all variants resolve to the same repo');

// Force the API-failure path so the deterministic .fail-link (built from the
// resolved repo) is what we assert on.
await page.route('https://api.github.com/**', (route) => route.fulfill({ status: 403 }));
await page.goto(`/#/${d.id}`);

// Default variant resolves to the device-level repo.
// Default variant resolves to its own repo (device-level or override).
await expect(page.locator('.release-notes .fail-link'))
.toHaveAttribute('href', `https://github.com/${d.repo}/releases`);
.toHaveAttribute('href', `https://github.com/${repoOf(startVariant)}/releases`);

// Selecting the override variant must re-render release notes against the override repo.
await page.locator(`#variant-seg button[data-variant="${overrideVariant}"]`).click();
// Selecting a variant with a different repo must re-render release notes against it.
await page.locator(`#variant-seg button[data-variant="${otherVariant}"]`).click();
await expect(page.locator('.release-notes .fail-link'))
.toHaveAttribute('href', `https://github.com/${overrideRepo}/releases`);
.toHaveAttribute('href', `https://github.com/${repoOf(otherVariant)}/releases`);
});

test('clicking the already-selected variant makes no extra release-notes fetch', async ({ page }) => {
Expand Down Expand Up @@ -275,28 +279,33 @@ test('header GitHub link and classic-installer links follow the selected variant
const d = registry.devices.find((x) => x.installers && x.installers.stable
&& Object.values(x.installers.stable).some((v) => v === null));
test.skip(!d, 'no device that hides a classic installer for a variant');
const hiddenVariant = Object.keys(d.installers.stable).find((k) => d.installers.stable[k] === null);
const defaultVariant = Object.keys(d.firmware.stable)[0];
test.skip(hiddenVariant === defaultVariant, 'hidden installer is on the default variant');
const overrideRepo = d.repos && d.repos.stable && d.repos.stable[hiddenVariant];
// The variant that hides its classic installer may be the default one, so assert
// the link toggling in whichever direction the registry happens to order them.
const variants = Object.keys(d.firmware.stable);
const installerOf = (v) => (v in d.installers.stable
? d.installers.stable[v] : d.githubPagesInstaller);
const repoOf = (v) => (d.repos && d.repos.stable && d.repos.stable[v]) || d.repo;
const startVariant = variants[0];
const otherVariant = variants.find((v) => !installerOf(v) !== !installerOf(startVariant));
test.skip(!otherVariant, 'every variant has the same classic-installer visibility');

await page.route('https://api.github.com/**', (route) => route.fulfill({ status: 403 }));
await page.goto(`/#/${d.id}`);

// Default variant: header GitHub = device repo, Classic installer link present.
// Default variant: header GitHub and Classic installer reflect that variant.
await expect(page.locator('.links a', { hasText: 'GitHub' }))
.toHaveAttribute('href', `https://github.com/${d.repo}`);
await expect(page.locator('.links a', { hasText: 'Classic installer' })).toHaveCount(1);
.toHaveAttribute('href', `https://github.com/${repoOf(startVariant)}`);
await expect(page.locator('.links a', { hasText: 'Classic installer' }))
.toHaveCount(installerOf(startVariant) ? 1 : 0);

// Select the variant whose installer is null.
await page.locator(`#variant-seg button[data-variant="${hiddenVariant}"]`).click();
// Select the variant on the other side of the null/non-null installer split.
await page.locator(`#variant-seg button[data-variant="${otherVariant}"]`).click();

// Header GitHub link now points at the override repo (if set); Classic installer link is gone.
if (overrideRepo) {
await expect(page.locator('.links a', { hasText: 'GitHub' }))
.toHaveAttribute('href', `https://github.com/${overrideRepo}`);
}
await expect(page.locator('.links a', { hasText: 'Classic installer' })).toHaveCount(0);
// Header GitHub link follows that variant's repo; installer link flips visibility.
await expect(page.locator('.links a', { hasText: 'GitHub' }))
.toHaveAttribute('href', `https://github.com/${repoOf(otherVariant)}`);
await expect(page.locator('.links a', { hasText: 'Classic installer' }))
.toHaveCount(installerOf(otherVariant) ? 1 : 0);
});

test('step 3 shows the Home Assistant hand-off', async ({ page }) => {
Expand Down
Loading