Fix Back button on SignIn page & Add Cancel button (#6415)
* Back button on SignIn page leads to Home page Fixes #6413 * Add regression test for the bug, and for new cancel button * Full sign in e2e test * Good bot Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --------- Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This commit is contained in:
		@ -29,11 +29,11 @@ test.describe('Electron app header tests', () => {
 | 
			
		||||
  test(
 | 
			
		||||
    'User settings has correct shortcut',
 | 
			
		||||
    { tag: '@electron' },
 | 
			
		||||
    async ({ page }, testInfo) => {
 | 
			
		||||
    async ({ page, toolbar }, testInfo) => {
 | 
			
		||||
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
      // Open the user sidebar menu.
 | 
			
		||||
      await page.getByTestId('user-sidebar-toggle').click()
 | 
			
		||||
      await toolbar.userSidebarButton.click()
 | 
			
		||||
 | 
			
		||||
      // No space after "User settings" since it's textContent.
 | 
			
		||||
      const text =
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										53
									
								
								e2e/playwright/auth.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e/playwright/auth.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
			
		||||
 | 
			
		||||
// test file is for testing auth functionality
 | 
			
		||||
test.describe('Authentication tests', () => {
 | 
			
		||||
  test(
 | 
			
		||||
    `The user can sign out and back in`,
 | 
			
		||||
    { tag: ['@electron'] },
 | 
			
		||||
    async ({ page, homePage, signInPage, toolbar, tronApp }) => {
 | 
			
		||||
      if (!tronApp) {
 | 
			
		||||
        fail()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
      await homePage.projectSection.waitFor()
 | 
			
		||||
 | 
			
		||||
      await test.step('Click on sign out and expect sign in page', async () => {
 | 
			
		||||
        await toolbar.userSidebarButton.click()
 | 
			
		||||
        await toolbar.signOutButton.click()
 | 
			
		||||
        await expect(signInPage.signInButton).toBeVisible()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Click on sign in and cancel, click again and expect different code', async () => {
 | 
			
		||||
        await signInPage.signInButton.click()
 | 
			
		||||
        await expect(signInPage.userCode).toBeVisible()
 | 
			
		||||
        const firstUserCode = await signInPage.userCode.textContent()
 | 
			
		||||
        await signInPage.cancelSignInButton.click()
 | 
			
		||||
        await expect(signInPage.signInButton).toBeVisible()
 | 
			
		||||
 | 
			
		||||
        await signInPage.signInButton.click()
 | 
			
		||||
        await expect(signInPage.userCode).toBeVisible()
 | 
			
		||||
        const secondUserCode = await signInPage.userCode.textContent()
 | 
			
		||||
        expect(secondUserCode).not.toEqual(firstUserCode)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Press back button and remain on home page', async () => {
 | 
			
		||||
        await page.goBack()
 | 
			
		||||
        await expect(homePage.projectSection).not.toBeVisible()
 | 
			
		||||
        await expect(signInPage.signInButton).toBeVisible()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Sign in, activate, and expect home page', async () => {
 | 
			
		||||
        await signInPage.signInButton.click()
 | 
			
		||||
        await expect(signInPage.userCode).toBeVisible()
 | 
			
		||||
        const userCode = await signInPage.userCode.textContent()
 | 
			
		||||
        expect(userCode).not.toBeNull()
 | 
			
		||||
        await signInPage.verifyAndConfirmAuth(userCode!)
 | 
			
		||||
 | 
			
		||||
        // Longer timeout than usual here for the wait on home page
 | 
			
		||||
        await expect(homePage.projectSection).toBeVisible({ timeout: 10000 })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
 | 
			
		||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
 | 
			
		||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
 | 
			
		||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
 | 
			
		||||
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
 | 
			
		||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | 
			
		||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
 | 
			
		||||
 | 
			
		||||
@ -66,6 +67,7 @@ export interface Fixtures {
 | 
			
		||||
  toolbar: ToolbarFixture
 | 
			
		||||
  scene: SceneFixture
 | 
			
		||||
  homePage: HomePageFixture
 | 
			
		||||
  signInPage: SignInPageFixture
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ElectronZoo {
 | 
			
		||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
 | 
			
		||||
  homePage: async ({ page }: { page: Page }, use: FnUse) => {
 | 
			
		||||
    await use(new HomePageFixture(page))
 | 
			
		||||
  },
 | 
			
		||||
  signInPage: async ({ page }: { page: Page }, use: FnUse) => {
 | 
			
		||||
    await use(new SignInPageFixture(page))
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (process.env.PLATFORM === 'web') {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								e2e/playwright/fixtures/signInPageFixture.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								e2e/playwright/fixtures/signInPageFixture.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import type { Locator, Page } from '@playwright/test'
 | 
			
		||||
import { secrets } from '@e2e/playwright/secrets'
 | 
			
		||||
 | 
			
		||||
export class SignInPageFixture {
 | 
			
		||||
  public page: Page
 | 
			
		||||
 | 
			
		||||
  signInButton!: Locator
 | 
			
		||||
  cancelSignInButton!: Locator
 | 
			
		||||
  userCode!: Locator
 | 
			
		||||
 | 
			
		||||
  apiBaseUrl!: string
 | 
			
		||||
 | 
			
		||||
  constructor(page: Page) {
 | 
			
		||||
    this.page = page
 | 
			
		||||
 | 
			
		||||
    this.signInButton = this.page.getByTestId('sign-in-button')
 | 
			
		||||
    this.cancelSignInButton = this.page.getByTestId('cancel-sign-in-button')
 | 
			
		||||
    this.userCode = this.page.getByTestId('sign-in-user-code')
 | 
			
		||||
 | 
			
		||||
    // TODO: set this thru env var
 | 
			
		||||
    this.apiBaseUrl = 'https://api.dev.zoo.dev'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async verifyAndConfirmAuth(userCode: string) {
 | 
			
		||||
    // Device flow: stolen from the tauri days
 | 
			
		||||
    // https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
 | 
			
		||||
    const headers = {
 | 
			
		||||
      Authorization: `Bearer ${secrets.token}`,
 | 
			
		||||
      Accept: 'application/json',
 | 
			
		||||
      'Content-Type': 'application/json',
 | 
			
		||||
    }
 | 
			
		||||
    const verifyUrl = `${this.apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
 | 
			
		||||
    console.log(`GET ${verifyUrl}`)
 | 
			
		||||
    const vr = await fetch(verifyUrl, { headers })
 | 
			
		||||
    console.log(vr.status)
 | 
			
		||||
 | 
			
		||||
    // Device flow: confirm
 | 
			
		||||
    const confirmUrl = `${this.apiBaseUrl}/oauth2/device/confirm`
 | 
			
		||||
    const data = JSON.stringify({ user_code: userCode })
 | 
			
		||||
    console.log(`POST ${confirmUrl} ${data}`)
 | 
			
		||||
    const cr = await fetch(confirmUrl, {
 | 
			
		||||
      headers,
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      body: data,
 | 
			
		||||
    })
 | 
			
		||||
    console.log(cr.status)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -46,6 +46,9 @@ export class ToolbarFixture {
 | 
			
		||||
  gizmo!: Locator
 | 
			
		||||
  gizmoDisabled!: Locator
 | 
			
		||||
  loadButton!: Locator
 | 
			
		||||
  /** User button for the user sidebar menu */
 | 
			
		||||
  userSidebarButton!: Locator
 | 
			
		||||
  signOutButton!: Locator
 | 
			
		||||
 | 
			
		||||
  constructor(page: Page) {
 | 
			
		||||
    this.page = page
 | 
			
		||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
 | 
			
		||||
    // element or two different elements can represent these states.
 | 
			
		||||
    this.gizmo = page.getByTestId('gizmo')
 | 
			
		||||
    this.gizmoDisabled = page.getByTestId('gizmo-disabled')
 | 
			
		||||
 | 
			
		||||
    this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
 | 
			
		||||
    this.signOutButton = page.getByTestId('user-sidebar-sign-out')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get logoLink() {
 | 
			
		||||
 | 
			
		||||
@ -331,6 +331,7 @@ test.describe('Onboarding tests', () => {
 | 
			
		||||
  test('Avatar text updates depending on image load success', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    homePage,
 | 
			
		||||
    tronApp,
 | 
			
		||||
  }) => {
 | 
			
		||||
@ -362,7 +363,7 @@ test.describe('Onboarding tests', () => {
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
    // Test that the text in this step is correct
 | 
			
		||||
    const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img')
 | 
			
		||||
    const avatarLocator = toolbar.userSidebarButton.locator('img')
 | 
			
		||||
    const onboardingOverlayLocator = page
 | 
			
		||||
      .getByTestId('onboarding-content')
 | 
			
		||||
      .locator('div')
 | 
			
		||||
@ -404,6 +405,7 @@ test.describe('Onboarding tests', () => {
 | 
			
		||||
  test("Avatar text doesn't mention avatar when no avatar", async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    homePage,
 | 
			
		||||
    tronApp,
 | 
			
		||||
  }) => {
 | 
			
		||||
@ -435,7 +437,7 @@ test.describe('Onboarding tests', () => {
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
    // Test that the text in this step is correct
 | 
			
		||||
    const sidebar = page.getByTestId('user-sidebar-toggle')
 | 
			
		||||
    const sidebar = toolbar.userSidebarButton
 | 
			
		||||
    const avatar = sidebar.locator('img')
 | 
			
		||||
    const onboardingOverlayLocator = page
 | 
			
		||||
      .getByTestId('onboarding-content')
 | 
			
		||||
@ -464,6 +466,7 @@ test.describe('Onboarding tests', () => {
 | 
			
		||||
test('Restarting onboarding on desktop takes one attempt', async ({
 | 
			
		||||
  context,
 | 
			
		||||
  page,
 | 
			
		||||
  toolbar,
 | 
			
		||||
  tronApp,
 | 
			
		||||
}) => {
 | 
			
		||||
  test.fixme(orRunWhenFullSuiteEnabled())
 | 
			
		||||
@ -502,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
 | 
			
		||||
    .filter({ hasText: 'Tutorial Project 00' })
 | 
			
		||||
  const tutorialModalText = page.getByText('Welcome to Design Studio!')
 | 
			
		||||
  const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
 | 
			
		||||
  const userMenuButton = page.getByTestId('user-sidebar-toggle')
 | 
			
		||||
  const userMenuButton = toolbar.userSidebarButton
 | 
			
		||||
  const userMenuSettingsButton = page.getByRole('button', {
 | 
			
		||||
    name: 'User settings',
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -1962,13 +1962,13 @@ test(
 | 
			
		||||
test(
 | 
			
		||||
  'Settings persist across restarts',
 | 
			
		||||
  { tag: '@electron' },
 | 
			
		||||
  async ({ page, scene, cmdBar }, testInfo) => {
 | 
			
		||||
  async ({ page, toolbar }, testInfo) => {
 | 
			
		||||
    await test.step('We can change a user setting like theme', async () => {
 | 
			
		||||
      await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
      page.on('console', console.log)
 | 
			
		||||
 | 
			
		||||
      await page.getByTestId('user-sidebar-toggle').click()
 | 
			
		||||
      await toolbar.userSidebarButton.click()
 | 
			
		||||
 | 
			
		||||
      await page.getByTestId('user-settings').click()
 | 
			
		||||
 | 
			
		||||
@ -1995,7 +1995,7 @@ test(
 | 
			
		||||
test(
 | 
			
		||||
  'Original project name persist after onboarding',
 | 
			
		||||
  { tag: '@electron' },
 | 
			
		||||
  async ({ page }, testInfo) => {
 | 
			
		||||
  async ({ page, toolbar }, testInfo) => {
 | 
			
		||||
    test.fixme(orRunWhenFullSuiteEnabled())
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
@ -2007,7 +2007,7 @@ test(
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Should go through onboarding', async () => {
 | 
			
		||||
      await page.getByTestId('user-sidebar-toggle').click()
 | 
			
		||||
      await toolbar.userSidebarButton.click()
 | 
			
		||||
      await page.getByTestId('user-settings').click()
 | 
			
		||||
      await page.getByRole('button', { name: 'Replay Onboarding' }).click()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ export function useAuthNavigation() {
 | 
			
		||||
 | 
			
		||||
  // Subscribe to the auth state of the app and navigate accordingly.
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    console.log('authState', authState.value)
 | 
			
		||||
    if (
 | 
			
		||||
      authState.matches('loggedIn') &&
 | 
			
		||||
      location.pathname.includes(PATHS.SIGN_IN)
 | 
			
		||||
@ -26,5 +27,5 @@ export function useAuthNavigation() {
 | 
			
		||||
    ) {
 | 
			
		||||
      navigate(PATHS.SIGN_IN)
 | 
			
		||||
    }
 | 
			
		||||
  }, [authState])
 | 
			
		||||
  }, [authState, location.pathname])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,11 @@ const SignIn = () => {
 | 
			
		||||
    authActor.send({ type: 'Log in', token })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const cancelSignIn = async () => {
 | 
			
		||||
    authActor.send({ type: 'Log out' })
 | 
			
		||||
    setUserCode('')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <main
 | 
			
		||||
      className="bg-primary h-screen grid place-items-stretch m-0 p-2"
 | 
			
		||||
@ -128,7 +133,10 @@ const SignIn = () => {
 | 
			
		||||
                    <p className="text-xs">
 | 
			
		||||
                      You should see the following code in your browser
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p className="text-lg font-bold inline-flex gap-1">
 | 
			
		||||
                    <p
 | 
			
		||||
                      className="text-lg font-bold inline-flex gap-1"
 | 
			
		||||
                      data-testid="sign-in-user-code"
 | 
			
		||||
                    >
 | 
			
		||||
                      {userCode.split('').map((char, i) => (
 | 
			
		||||
                        <span
 | 
			
		||||
                          key={i}
 | 
			
		||||
@ -141,6 +149,17 @@ const SignIn = () => {
 | 
			
		||||
                        </span>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <button
 | 
			
		||||
                      onClick={toSync(cancelSignIn, reportRejection)}
 | 
			
		||||
                      className={
 | 
			
		||||
                        'm-0 mt-8 w-fit flex gap-4 items-center px-3 py-1 ' +
 | 
			
		||||
                        '!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
 | 
			
		||||
                      }
 | 
			
		||||
                      data-testid="cancel-sign-in-button"
 | 
			
		||||
                    >
 | 
			
		||||
                      <CustomIcon name="arrowLeft" className="w-6 h-6" />
 | 
			
		||||
                      Cancel
 | 
			
		||||
                    </button>
 | 
			
		||||
                  </>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
@ -187,7 +206,7 @@ const SignIn = () => {
 | 
			
		||||
                'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
 | 
			
		||||
                '!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
 | 
			
		||||
              }
 | 
			
		||||
              data-testid="sign-in-button"
 | 
			
		||||
              data-testid="view-sample-button"
 | 
			
		||||
            >
 | 
			
		||||
              View this sample
 | 
			
		||||
              <CustomIcon name="arrowRight" className="w-6 h-6" />
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user