diff --git a/test/e2e/constants/config.ts b/test/e2e/constants/config.ts index c1e31607..1a5e6f66 100644 --- a/test/e2e/constants/config.ts +++ b/test/e2e/constants/config.ts @@ -11,3 +11,16 @@ export const e2eTestDirPath = join(__dirname, '..') * files outside of the e2e test directory. */ export const rootDirPath = join(__dirname, '../../..') + +/** + * The path to the node home directory. This is where the Radicle node stores its + * configuration and data. + */ +export const nodeHomePath = process.env['RAD_HOME'] + +/** + * The path to a backup node home directory. This is used to store the original + * node home directory for tests that require the original path to be moved or + * modified. + */ +export const backupNodeHomePath = `${nodeHomePath}.backup` diff --git a/test/e2e/helpers/actions.ts b/test/e2e/helpers/actions.ts new file mode 100644 index 00000000..b9fabbcf --- /dev/null +++ b/test/e2e/helpers/actions.ts @@ -0,0 +1,15 @@ +import type { Workbench } from 'wdio-vscode-service' + +/** + * Opens the Radicle view container in the sidebar, by clicking the radicle button in the + * activity bar. + */ +export async function openRadicleViewContainer(workbench: Workbench) { + const activityBar = workbench.getActivityBar() + await activityBar.wait() + + const radicleViewControl = await activityBar.getViewControl('Radicle') + await radicleViewControl?.wait() + + await radicleViewControl?.openView() +} diff --git a/test/e2e/helpers/assertions.ts b/test/e2e/helpers/assertions.ts new file mode 100644 index 00000000..539d5c31 --- /dev/null +++ b/test/e2e/helpers/assertions.ts @@ -0,0 +1,28 @@ +import { browser } from '@wdio/globals' +import type { Workbench } from 'wdio-vscode-service' + +/** + * Asserts that the CLI Commands and Patches sections are visible in the sidebar. This is + * considered the default state when the workspace is open with a git and rad initialized + * repository. + */ +export async function expectStandardSidebarViewsToBeVisible(workbench: Workbench) { + const sidebarView = workbench.getSideBar().getContent() + await sidebarView.wait() + + await browser.waitUntil( + async () => { + try { + await Promise.all([ + sidebarView.getSection('CLI COMMANDS'), + sidebarView.getSection('PATCHES'), + ]) + + return true + } catch { + return false + } + }, + { timeoutMsg: 'expected the standard sidebar views to be visible' }, + ) +} diff --git a/test/e2e/helpers/queries.ts b/test/e2e/helpers/queries.ts new file mode 100644 index 00000000..1efa6d57 --- /dev/null +++ b/test/e2e/helpers/queries.ts @@ -0,0 +1,21 @@ +import type { Workbench } from 'wdio-vscode-service' + +/** + * Retrieves the welcome text content from the first section in the sidebar view. + * + * @example await getFirstWelcomeViewText(workbench) // ["Welcome to Radicle!", "Get started by ..."] + * + * @returns The text content found as an array of strings split by newlines. If no content is + * found, an empty array. + */ +export async function getFirstWelcomeViewText(workbench: Workbench) { + const sidebarView = workbench.getSideBar().getContent() + await sidebarView.wait() + + const welcomeText = + (await ( + await (await sidebarView.getSections())[0]?.findWelcomeContent() + )?.getTextSections()) ?? [] + + return welcomeText +} diff --git a/test/e2e/specs/onboarding.spec.ts b/test/e2e/specs/onboarding.spec.ts index 071599ee..fff8b4e2 100644 --- a/test/e2e/specs/onboarding.spec.ts +++ b/test/e2e/specs/onboarding.spec.ts @@ -1,19 +1,27 @@ import path from 'node:path' import { browser, expect } from '@wdio/globals' -import type { ViewSection, Workbench } from 'wdio-vscode-service' +import type { Workbench } from 'wdio-vscode-service' import { $, cd } from 'zx' import type * as VsCode from 'vscode' -import { e2eTestDirPath } from '../constants/config' +import isEqual from 'lodash/isEqual' +import { expectStandardSidebarViewsToBeVisible } from '../helpers/assertions' +import { openRadicleViewContainer } from '../helpers/actions' +import { getFirstWelcomeViewText } from '../helpers/queries' +import { backupNodeHomePath, e2eTestDirPath, nodeHomePath } from '../constants/config' describe('Onboarding Flow', () => { let workbench: Workbench before(async () => { - await initGitRepo() workbench = await browser.getWorkbench() }) - describe('VS Code, *before* the workspace is rad-initialized,', () => { + describe('VS Code, *before* Radicle is installed,', () => { + after(async () => { + await $`mv ${backupNodeHomePath} ${nodeHomePath}` + await workbench.executeCommand('Developer: Reload Window') + }) + it('has our Radicle extension installed and available', async () => { const extensions = await browser.executeWorkbench( (vscode: typeof VsCode) => vscode.extensions.all, @@ -31,68 +39,92 @@ describe('Onboarding Flow', () => { expect(title).toBe('Radicle') }) - it('guides the user on how to rad-initialize their git repo', async () => { + it('instructs the user to install radicle', async () => { await openRadicleViewContainer(workbench) const welcomeText = await getFirstWelcomeViewText(workbench) + const buttonTitles = await getFirstWelcomeViewButtonTitles(workbench) expect(welcomeText).toEqual([ /* eslint-disable max-len */ - 'The Git repository currently opened in your workspace is not yet initialized with Radicle.', - 'To use Radicle with it, please run `rad init` in your terminal.', - 'Once rad-initialized, this repo will have access to advanced source control, collaboration and project management capabilities powered by both Git and Radicle.', - 'During this reversible rad-initializing process you also get to choose whether your repo will be private or public, among other options.', - 'To learn more read the Radicle User Guide.', + 'Failed resolving the Radicle CLI binary.', + "Please ensure it is installed on your machine and either that it is globally accessible in the shell as `rad` or that its path is correctly defined in the extension's settings.", + "Please expect the extention's capabilities to remain severely limited until this issue is resolved.", /* eslint-enable max-len */ ]) - expect(welcomeText.some((text) => text.includes('rad init'))).toBe(true) + expect(buttonTitles).toEqual(['Troubleshoot']) }) }) - describe('VS Code, *after* the workspace is rad-initialized,', () => { - let cliCommandsSection: ViewSection + describe('VS Code, *before* the workspace is git-initialized,', () => { + it('guides the user on how to git-initialize their workspace', async () => { + await openRadicleViewContainer(workbench) + + await browser.waitUntil(async () => { + const welcomeText = await getFirstWelcomeViewText(workbench) + const welcomeButtonTitles = await getFirstWelcomeViewButtonTitles(workbench) + + return ( + isEqual(welcomeText, [ + /* eslint-disable max-len */ + 'The folder currently opened in your workspace is not a Git code repository.', + 'In order to use Radicle with it, this folder must first be initialized as a Git code repository.', + 'To learn more about how to use Git and source control in VS Code read the docs.', + /* eslint-enable max-len */ + ]) && + isEqual(welcomeButtonTitles, [ + 'Initialize Repository With Git', + 'Choose a Different Folder', + ]) + ) + }) + }) + }) + describe('VS Code, *before* the workspace is rad-initialized,', () => { before(async () => { - await $`rad init --private --default-branch main --name "A_test_blog" --description "Some repo" --no-confirm --verbose` + await initGitRepo() + }) + + it('guides the user on how to rad-initialize their git repo', async () => { await openRadicleViewContainer(workbench) - const sidebarView = workbench.getSideBar().getContent() - await sidebarView.wait() - cliCommandsSection = await sidebarView.getSection('CLI COMMANDS') - await cliCommandsSection.collapse() + await browser.waitUntil(async () => { + const welcomeText = await getFirstWelcomeViewText(workbench) + + return isEqual(welcomeText, [ + /* eslint-disable max-len */ + 'The Git repository currently opened in your workspace is not yet initialized with Radicle.', + 'To use Radicle with it, please run `rad init` in your terminal.', + 'Once rad-initialized, this repo will have access to advanced source control, collaboration and project management capabilities powered by both Git and Radicle.', + 'During this reversible rad-initializing process you also get to choose whether your repo will be private or public, among other options.', + 'To learn more read the Radicle User Guide.', + /* eslint-enable max-len */ + ]) + }) + }) + }) - const patchesSection = await sidebarView.getSection('PATCHES') - await patchesSection.collapse() + describe('VS Code, *after* the workspace is rad-initialized,', () => { + before(async () => { + await $`rad init --private --default-branch main --name "A_test_blog" --description "Some repo" --no-confirm --verbose` + await workbench.executeCommand('Developer: Reload Window') }) it('hides the non rad-initialized guide', async () => { - const welcomeText = await getFirstWelcomeViewText(workbench) + await browser.waitUntil(async () => { + const welcomeText = await getFirstWelcomeViewText(workbench) - expect(welcomeText.some((text) => text.includes('rad init'))).not.toBe(true) + return welcomeText.some((text) => text.includes('rad init')) === false + }) }) - it('shows the CLI Commands section', async () => { - await cliCommandsSection.expand() - // Fixes flakiness on macOS CI - await browser.pause(100) - - const welcomeContent = await cliCommandsSection?.findWelcomeContent() - const welcomeText = (await welcomeContent?.getTextSections()) ?? [] - const buttons = (await welcomeContent?.getButtons()) ?? [] - const buttonTitles = await Promise.all( - buttons.map(async (button) => await button.getTitle()), - ) - - expect( - welcomeText.some((text) => - /Use the buttons below to perform common interactions with the Radicle network./i.test( - text, - ), - ), - ).toBe(true) + it('shows the standard sidebar views', async () => { + const sidebarView = workbench.getSideBar().getContent() + await sidebarView.wait() - expect(buttonTitles).toEqual(['Sync', 'Fetch', 'Announce']) + await expectStandardSidebarViewsToBeVisible(workbench) }) }) }) @@ -111,24 +143,16 @@ async function initGitRepo() { await $`git commit -m 'adds readme' --no-gpg-sign` } -async function openRadicleViewContainer(workbench: Workbench) { - const activityBar = workbench.getActivityBar() - await activityBar.wait() - - const radicleViewControl = await activityBar.getViewControl('Radicle') - await radicleViewControl?.wait() - - await radicleViewControl?.openView() -} - -async function getFirstWelcomeViewText(workbench: Workbench) { +async function getFirstWelcomeViewButtonTitles(workbench: Workbench) { const sidebarView = workbench.getSideBar().getContent() await sidebarView.wait() - const welcomeText = - (await ( - await (await sidebarView.getSections())[0]?.findWelcomeContent() - )?.getTextSections()) ?? [] + const buttons = + (await (await (await sidebarView.getSections())[0]?.findWelcomeContent())?.getButtons()) ?? + [] + const buttonTitles = await Promise.all( + buttons.map(async (button) => await button.getTitle()), + ) - return welcomeText + return buttonTitles } diff --git a/test/e2e/wdio.conf.ts b/test/e2e/wdio.conf.ts index 1844457d..d95a3414 100644 --- a/test/e2e/wdio.conf.ts +++ b/test/e2e/wdio.conf.ts @@ -1,7 +1,12 @@ import path from 'node:path' import { $ } from 'zx' import type { Options } from '@wdio/types' -import { e2eTestDirPath, rootDirPath } from './constants/config' +import { + backupNodeHomePath, + e2eTestDirPath, + nodeHomePath, + rootDirPath, +} from './constants/config' // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const packageJson = require('../../package.json') @@ -56,4 +61,9 @@ export const config: Options.Testrunner = { onPrepare: async () => { await $`mkdir -p ${path.join(rootDirPath, 'node_modules/.cache/wdio')}` }, + onWorkerStart: async (_cid, _caps, specs) => { + if (specs.some((spec) => spec.includes('onboarding.spec.ts'))) { + await $`mv ${nodeHomePath} ${backupNodeHomePath}` + } + }, }