Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BADGER-223][WIP] Redux-based refactor of Desktop #739

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
de4c977
Initial pass at a Redux-powered Desktop
markspolakovs Oct 26, 2024
03b3de5
Add some docs and reorganisation
markspolakovs Oct 26, 2024
4663ec2
Show selection and settings persistence
markspolakovs Oct 26, 2024
f2bb41f
Install react/redux devtools + tidy try-connect preflight
markspolakovs Oct 26, 2024
98619ba
Working OBS connection
markspolakovs Oct 27, 2024
f3c8145
Add some more docs
markspolakovs Oct 27, 2024
dc96c55
Allow developing in a browser
markspolakovs Nov 2, 2024
957d3ac
Bundle twiddling
markspolakovs Nov 2, 2024
7ae98c3
Fix main dev (switch to vite-node)
markspolakovs Nov 2, 2024
4ab4db2
Add initial Ontime state
markspolakovs Nov 2, 2024
6340635
Speed up preflight by only waiting for the actions we need
markspolakovs Nov 2, 2024
8c864af
Port ontime gui and push
markspolakovs Nov 2, 2024
d1a8fd8
Implement most remaining features (inc vMix)
markspolakovs Nov 3, 2024
5cad36e
Small fixups
markspolakovs Nov 5, 2024
d8e76c2
Add global alerts
markspolakovs Nov 9, 2024
80a2d77
Try to sort out settings defaulting and desktop standalone/electron t…
markspolakovs Nov 26, 2024
641a023
Fix settings defaulting (and add redux remote)
markspolakovs Dec 27, 2024
ba56614
FIXME out big show test
markspolakovs Dec 28, 2024
045ccb1
Tidying
markspolakovs Dec 28, 2024
fd18196
Add mise
markspolakovs Dec 28, 2024
4be207b
Remove duplicate tickets from Danger comment
markspolakovs Dec 28, 2024
a99c7a8
Include FIXME lines in Danger comment
markspolakovs Dec 28, 2024
56cc864
Yarn dedupe
markspolakovs Dec 28, 2024
d1fe06c
Port DevTools (and tidy up renderer folder)
markspolakovs Dec 28, 2024
1345c73
Setting log level at runtime
markspolakovs Dec 28, 2024
aa5744b
useless log
markspolakovs Dec 28, 2024
1a28420
Port obsCallArbitrary
markspolakovs Dec 29, 2024
25005e6
typecheck low hanging fruit
markspolakovs Dec 29, 2024
f411393
Fix obsHelpers.test.ts
markspolakovs Dec 29, 2024
7393939
Port mediaSettings (inc deleteOldMedia)
markspolakovs Dec 29, 2024
565a91d
Exclude ipcApi--old from typecheck
markspolakovs Dec 29, 2024
fa6f4c3
Fix desktop CI
markspolakovs Dec 29, 2024
f7ec14f
Fix? typecheck
markspolakovs Dec 29, 2024
d756923
Run tests in Electron
markspolakovs Dec 29, 2024
d254cca
derp
markspolakovs Dec 29, 2024
e7e3666
Remove renderer tests
markspolakovs Dec 29, 2024
26f359f
actions
markspolakovs Dec 29, 2024
69d4e72
Unconditionally set up playwright
markspolakovs Dec 29, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ jobs:
working-directory: ./desktop
env:
TEST_APPLICATION_PATH: ${{ runner.temp }}\badger\Badger Desktop.exe
ELECTRON: "true"

linear:
needs: [test-e2e-server, test-desktop]
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ jobs:
PLAYWRIGHT_HTML_REPORT: ${{ github.workspace }}/server/playwright-report

- uses: actions/upload-artifact@v3
if: always()
if: failure()
with:
name: playwright-report-server
path: ./server/playwright-report/
Expand All @@ -187,10 +187,13 @@ jobs:
test-e2e-desktop-standalone:
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
matrix:
ELECTRON: [true, false]
env:
NODE_ENV: test
E2E_TEST: "true"
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: ${{ matrix.ELECTRON == 'true' && 1 || 0 }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js 18.x
Expand All @@ -210,15 +213,20 @@ jobs:
- name: Make logs folder
run: mkdir -p ${{ runner.temp }}/logs

- uses: ./.github/steps/setup-playwright
with:
working-directory: ./desktop

- name: Run Playwright tests
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn ${{ runner.debug && 'test:e2e:debug' || 'test:e2e' }} --project standalone
working-directory: ./desktop
env:
PLAYWRIGHT_HTML_REPORT: ${{ github.workspace }}/desktop/playwright-report
BADGER_LOGS_PATH: ${{ runner.temp }}/logs
ELECTRON: ${{ matrix.ELECTRON }}

- uses: actions/upload-artifact@v3
if: always()
if: failure()
with:
name: playwright-report-desktop
path: |
Expand Down Expand Up @@ -297,9 +305,10 @@ jobs:
env:
PLAYWRIGHT_HTML_REPORT: ${{ github.workspace }}/desktop/playwright-report
BADGER_LOGS_PATH: ${{ runner.temp }}/logs
ELECTRON: "true"

- uses: actions/upload-artifact@v3
if: always()
if: failure()
with:
name: playwright-report-desktop
path: |
Expand Down
7 changes: 7 additions & 0 deletions .mise-tasks/build/desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

#MISE dir="{{config_root}}/desktop"
#MISE sources=["{{config_root}}/desktop/src/**/*", "{{config_root}}/desktop/*.cjs", "{{config_root}}/desktop/*.mjs", "{{config_root}}/desktop/*.ts", "{{config_root}}/desktop/*.mts", "{{config_root}}/desktop/package.json"]
#MISE outputs=["{{config_root}}/desktop/out/**/*"]
rm -rf out
node ../node_modules/.bin/electron-vite build
20 changes: 20 additions & 0 deletions .mise-tasks/test/desktop/e2e/standalone
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

#MISE env={E2E_TEST = "true"}
#MISE dir="{{config_root}}/desktop"
#MISE depends="build:desktop"
#USAGE flag "--headed" help="Run tests in headed mode"
#USAGE flag "-d --debug-webserver" help="Run tests in debug mode"
#USAGE flag "-e --electron" help="Run tests in Electron"
playwright_args=()
if [ -n "$usage_headed" ]; then
playwright_args+=("--headed")
fi
if [ -n "$usage_electron" ]; then
export ELECTRON=true
fi
if [ -n "$usage_debug" ]; then
export DEBUG='pw:api*,pw:browser*'
fi
export E2E_TEST=true
npx playwright test --project standalone "${playwright_args[@]}"
14 changes: 12 additions & 2 deletions Dangerfile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { message, danger, fail, warn } from "danger";

const issueKeyRe = /(BDGR-\d+)/g;
const issueKeyRe = /(BA?DGE?R-\d+)/g;

async function findAddedAndRemovedTodoIssues() {
const removed = new Set<string>();
Expand Down Expand Up @@ -41,6 +41,12 @@ async function findAddedAndRemovedTodoIssues() {
}
}
}
for (const rm of removed) {
if (added.has(rm)) {
removed.delete(rm);
added.delete(rm);
}
}
return { removed, added, linesWithoutKey, fixmes };
}

Expand Down Expand Up @@ -75,7 +81,11 @@ You can also include \`Closes ${Array.from(removed).join(
}
if (fixmes.size > 0) {
fail(
`Found ${fixmes.size} FIXME comments. Please either remove them or convert them to TODOs (with an associated Linear ticket).`,
`Found ${fixmes.size} FIXME comments. Please either remove them or convert them to TODOs (with an associated Linear ticket): \n${Array.from(
fixmes,
)
.map((l) => " * `" + l + "`")
.join("\n")}`,
);
}
};
Expand Down
3 changes: 2 additions & 1 deletion desktop/.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
DESKTOP_SENTRY_DSN=https://[email protected]/789x`
DESKTOP_SENTRY_DSN=https://[email protected]/789x
VITE_DESKTOP_SENTRY_DSN=$DESKTOP_SENTRY_DSN
ENVIRONMENT=localhost
VITE_ENVIRONMENT=$ENVIRONMENT
BADGER_ENABLE_REDUX_DEVTOOLS=true
3 changes: 3 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,6 @@ dist/

# Bundle visualization
bundle-*.html

# Settings
.dev/
156 changes: 104 additions & 52 deletions desktop/e2e/standalone/base.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-empty-pattern */
/* eslint-disable no-console */
import {
test as base,
Expand All @@ -17,71 +18,122 @@ const MICRO_SERVER_PORT = process.env.MICRO_SERVER_PORT
: 8594;
const MICRO_SERVER_PASSWORD = "microserver";

const test = base.extend<{
const ELECTRON = Boolean(process.env.TEST_USE_ELECTRON);

let test = base.extend<{
scenario: string;
enabledIntegrations: ("obs" | "ontime" | "vmix")[];
testMediaPath: string;
app: [ElectronApplication, Page];
}>({
scenario: "default",
enabledIntegrations: ["obs", "ontime", "vmix"],
// eslint-disable-next-line no-empty-pattern
testMediaPath: async ({}, use) => {
const dir = await fsp.mkdtemp(
join(tmpdir(), "badger-desktop-e2e-standalone"),
);
await use(dir);
await fsp.rm(dir, { recursive: true });
},

app: async ({ scenario, testMediaPath }, use, testInfo) => {
// Allow running tests on a built / installed app
const electronPath = process.env.TEST_APPLICATION_PATH;
const app = await electron.launch({
args: electronPath
? ["--enable-logging"]
: ["--enable-logging", "out/main/index.js"],
executablePath: electronPath,
env: {
...process.env,
NODE_ENV: "test",
E2E_TEST: "true",
__USE_MOCK_VMIX: "true",
__TEST_SETTINGS_MEDIA: `{ "mediaPath": ${JSON.stringify(testMediaPath)} }`,
__TEST_SUPPORTED_INTEGRATIONS: JSON.stringify([
"obs",
"ontime",
"vmix",
]),
},
});

const win = await app.firstWindow();

await win.context().tracing.start({ screenshots: true, snapshots: true });

await win.waitForLoadState("domcontentloaded");

await win
.getByLabel("Server address")
.fill(`http://localhost:${MICRO_SERVER_PORT}/${scenario}`);
await win.getByLabel("Server Password").fill(MICRO_SERVER_PASSWORD);

await win.getByRole("button", { name: "Connect" }).click();

await expect(
win.getByRole("heading", { name: "Select a show" }),
).toBeVisible();

await use([app, win]);

await win
.context()
.tracing.stop({ path: `traces/${testInfo.title}-${testInfo.retry}.zip` });

await win.close();
await app.close();
},
});

if (ELECTRON) {
test = test.extend<{ app: [ElectronApplication, Page]; page: Page }>({
app: async ({ scenario, testMediaPath }, use, testInfo) => {
// Allow running tests on a built / installed app
const electronPath = process.env.TEST_APPLICATION_PATH;
const app = await electron.launch({
args: electronPath
? ["--enable-logging"]
: ["--enable-logging", "out/main/index.js"],
executablePath: electronPath,
env: {
...process.env,
NODE_ENV: "test",
E2E_TEST: "true",
__USE_MOCK_VMIX: "true",
__TEST_SETTINGS_MEDIA: `{ "mediaPath": ${JSON.stringify(testMediaPath)} }`,
__TEST_SUPPORTED_INTEGRATIONS: JSON.stringify([
"obs",
"ontime",
"vmix",
]),
},
});

const win = await app.firstWindow();

await win.context().tracing.start({ screenshots: true, snapshots: true });

await win.waitForLoadState("domcontentloaded");

await win
.getByLabel("Server address")
.fill(`http://localhost:${MICRO_SERVER_PORT}/${scenario}`);
await win.getByLabel("Server Password").fill(MICRO_SERVER_PASSWORD);

await win.getByRole("button", { name: "Connect" }).click();

await expect(
win.getByRole("heading", { name: "Select a show" }),
).toBeVisible();

await use([app, win]);

await win.context().tracing.stop({
path: `traces/${testInfo.title}-${testInfo.retry}.zip`,
});

await win.close();
await app.close();
},
page: async ({ app }, use) => {
const [_, page] = app;
await use(page);
},
});
} else {
test = test.extend<{ app: [ElectronApplication, Page]; page: Page }>({
app: async ({}, _use, testInfo) => {
testInfo.skip(true, "Not running in Electron");
},
page: async ({ browser, scenario, request, testMediaPath }, use) => {
await request.post("http://localhost:5174/reset", {
data: {
settings: {
media: {
mediaPath: testMediaPath,
},
},
integrations: {
supported: ["obs", "ontime", "vmix"],
},
},
});

const page = await browser.newPage({
baseURL: `http://localhost:5173`,
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
});
await page.goto("/");

await page.waitForLoadState("domcontentloaded");

await page
.getByLabel("Server address")
.fill(`http://localhost:${MICRO_SERVER_PORT}/${scenario}`);
await page.getByLabel("Server Password").fill(MICRO_SERVER_PASSWORD);

await page.getByRole("button", { name: "Connect" }).click();

await expect(
page.getByRole("heading", { name: "Select a show" }),
).toBeVisible();

await use(page);
await page.close();
},
});
}

export { test, expect };
4 changes: 1 addition & 3 deletions desktop/e2e/standalone/obs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import MockOBSWebSocket from "@badger/testing/MockOBSWebSocket";
import { test, expect } from "./base";

test("download continuity media and load into OBS", async ({
app: [_, page],
}) => {
test("download continuity media and load into OBS", async ({ page }) => {
const mows = await MockOBSWebSocket.create(expect, async (obs) => {
obs.alwaysRespond("GetVersion", () => ({
success: true,
Expand Down
30 changes: 16 additions & 14 deletions desktop/e2e/standalone/standalone.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { test, expect } from "./base";

test("it works", async ({ app: [_, page] }) => {
test("it works", async ({ page }) => {
await page.getByText("Test show").click();
});

test.describe("big show", () => {
test.use({ scenario: "big-show" });
test("scrolling for a show with lots of rundown items", async ({
app: [_, page],
}) => {
await page.getByRole("button", { name: "Select" }).click();
// TODO[BADGER-180]: Need new vmix mocks in place
test.fixme(
"scrolling for a show with lots of rundown items",
async ({ page }) => {
await page.getByRole("button", { name: "Select" }).click();

await page.getByText("Continuity").click();
await page.getByRole("menuitem", { name: "Test Rundown" }).click();
await page.getByText("Continuity").click();
await page.getByRole("menuitem", { name: "Test Rundown" }).click();

await page
.getByRole("cell", { name: "Test Item 40" })
.scrollIntoViewIfNeeded();
await expect(
page.getByRole("cell", { name: "Test Item 40" }),
).toBeInViewport();
});
await page
.getByRole("cell", { name: "Test Item 40" })
.scrollIntoViewIfNeeded();
await expect(
page.getByRole("cell", { name: "Test Item 40" }),
).toBeInViewport();
},
);
});
Loading
Loading