2024-03-06 12:08:15 +11:00
|
|
|
import { test, expect, Download } from '@playwright/test'
|
2023-11-24 08:59:24 +11:00
|
|
|
import { secrets } from './secrets'
|
|
|
|
import { getUtils } from './test-utils'
|
2023-11-29 11:20:23 +11:00
|
|
|
import { Models } from '@kittycad/lib'
|
|
|
|
import fsp from 'fs/promises'
|
2023-12-18 13:17:09 +11:00
|
|
|
import { spawn } from 'child_process'
|
2023-12-18 06:15:26 -05:00
|
|
|
import { APP_NAME } from 'lib/constants'
|
2024-03-14 11:50:46 -04:00
|
|
|
import JSZip from 'jszip'
|
|
|
|
import path from 'path'
|
2024-04-02 10:29:34 -04:00
|
|
|
import { basicSettings, basicStorageState } from './storageStates'
|
|
|
|
import * as TOML from '@iarna/toml'
|
2023-11-24 08:59:24 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
test.beforeEach(async ({ page }) => {
|
2023-11-24 08:59:24 +11:00
|
|
|
// reducedMotion kills animations, which speeds up tests and reduces flakiness
|
|
|
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
|
|
|
})
|
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
test.use({
|
|
|
|
storageState: structuredClone(basicStorageState),
|
|
|
|
})
|
|
|
|
|
2024-03-05 11:52:43 +11:00
|
|
|
test.setTimeout(60_000)
|
2023-11-24 08:59:24 +11:00
|
|
|
|
2024-02-27 09:41:09 +11:00
|
|
|
test('exports of each format should work', async ({ page, context }) => {
|
|
|
|
// FYI this test doesn't work with only engine running locally
|
|
|
|
// And you will need to have the KittyCAD CLI installed
|
|
|
|
const u = getUtils(page)
|
|
|
|
await context.addInitScript(async () => {
|
|
|
|
;(window as any).playwrightSkipFilePicker = true
|
|
|
|
localStorage.setItem(
|
|
|
|
'persistCode',
|
|
|
|
`const topAng = 25
|
|
|
|
const bottomAng = 35
|
|
|
|
const baseLen = 3.5
|
|
|
|
const baseHeight = 1
|
|
|
|
const totalHeightHalf = 2
|
|
|
|
const armThick = 0.5
|
|
|
|
const totalLen = 9.5
|
|
|
|
const part001 = startSketchOn('-XZ')
|
|
|
|
|> startProfileAt([0, 0], %)
|
|
|
|
|> yLine(baseHeight, %)
|
|
|
|
|> xLine(baseLen, %)
|
|
|
|
|> angledLineToY({
|
|
|
|
angle: topAng,
|
|
|
|
to: totalHeightHalf,
|
2024-03-15 17:03:42 -04:00
|
|
|
}, %, 'seg04')
|
|
|
|
|> xLineTo(totalLen, %, 'seg03')
|
|
|
|
|> yLine(-armThick, %, 'seg01')
|
2024-02-27 09:41:09 +11:00
|
|
|
|> angledLineThatIntersects({
|
|
|
|
angle: HALF_TURN,
|
|
|
|
offset: -armThick,
|
|
|
|
intersectTag: 'seg04'
|
|
|
|
}, %)
|
|
|
|
|> angledLineToY([segAng('seg04', %) + 180, ZERO], %)
|
|
|
|
|> angledLineToY({
|
|
|
|
angle: -bottomAng,
|
|
|
|
to: -totalHeightHalf - armThick,
|
2024-03-15 17:03:42 -04:00
|
|
|
}, %, 'seg02')
|
2024-02-27 09:41:09 +11:00
|
|
|
|> xLineTo(segEndX('seg03', %) + 0, %)
|
|
|
|
|> yLine(-segLen('seg01', %), %)
|
|
|
|
|> angledLineThatIntersects({
|
|
|
|
angle: HALF_TURN,
|
|
|
|
offset: -armThick,
|
|
|
|
intersectTag: 'seg02'
|
|
|
|
}, %)
|
|
|
|
|> angledLineToY([segAng('seg02', %) + 180, -baseHeight], %)
|
|
|
|
|> xLineTo(ZERO, %)
|
|
|
|
|> close(%)
|
|
|
|
|> extrude(4, %)`
|
|
|
|
)
|
|
|
|
})
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await u.openDebugPanel()
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.waitForCmdReceive('extrude')
|
|
|
|
await page.waitForTimeout(1000)
|
|
|
|
await u.clearAndCloseDebugPanel()
|
|
|
|
|
|
|
|
interface Paths {
|
|
|
|
modelPath: string
|
|
|
|
imagePath: string
|
|
|
|
outputType: string
|
|
|
|
}
|
|
|
|
const doExport = async (
|
|
|
|
output: Models['OutputFormat_type']
|
|
|
|
): Promise<Paths> => {
|
2024-03-04 16:06:43 -05:00
|
|
|
await page.getByRole('button', { name: APP_NAME }).click()
|
2024-03-06 02:42:27 +11:00
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Export Part' })
|
|
|
|
).toBeVisible()
|
2024-03-04 16:06:43 -05:00
|
|
|
await page.getByRole('button', { name: 'Export Part' }).click()
|
2024-03-06 02:42:27 +11:00
|
|
|
await expect(page.getByTestId('command-bar')).toBeVisible()
|
2024-02-27 09:41:09 +11:00
|
|
|
|
2024-03-04 16:06:43 -05:00
|
|
|
// Go through export via command bar
|
|
|
|
await page.getByRole('option', { name: output.type, exact: false }).click()
|
2024-03-06 02:42:27 +11:00
|
|
|
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
2024-02-27 09:41:09 +11:00
|
|
|
if ('storage' in output) {
|
2024-03-06 02:42:27 +11:00
|
|
|
await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
|
2024-03-04 16:06:43 -05:00
|
|
|
await page.getByRole('button', { name: 'storage', exact: false }).click()
|
|
|
|
await page
|
|
|
|
.getByRole('option', { name: output.storage, exact: false })
|
|
|
|
.click()
|
2024-03-06 02:42:27 +11:00
|
|
|
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
2024-02-27 09:41:09 +11:00
|
|
|
}
|
2024-03-06 02:42:27 +11:00
|
|
|
await expect(page.getByText('Confirm Export')).toBeVisible()
|
|
|
|
|
2024-03-06 12:08:15 +11:00
|
|
|
const getPromiseAndResolve = () => {
|
|
|
|
let resolve: any = () => {}
|
|
|
|
const promise = new Promise<Download>((r) => {
|
|
|
|
resolve = r
|
|
|
|
})
|
|
|
|
return [promise, resolve]
|
|
|
|
}
|
|
|
|
|
|
|
|
const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
|
|
|
|
let downloadCnt = 0
|
|
|
|
|
|
|
|
page.on('download', async (download) => {
|
|
|
|
if (downloadCnt === 0) {
|
|
|
|
downloadResolve1(download)
|
|
|
|
}
|
|
|
|
downloadCnt++
|
|
|
|
})
|
2024-03-04 16:06:43 -05:00
|
|
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
2024-02-27 09:41:09 +11:00
|
|
|
|
2024-03-04 16:06:43 -05:00
|
|
|
// Handle download
|
2024-03-06 12:08:15 +11:00
|
|
|
const download = await downloadPromise1
|
2024-02-27 09:41:09 +11:00
|
|
|
const downloadLocationer = (extra = '', isImage = false) =>
|
|
|
|
`./e2e/playwright/export-snapshots/${output.type}-${
|
|
|
|
'storage' in output ? output.storage : ''
|
|
|
|
}${extra}.${isImage ? 'png' : output.type}`
|
|
|
|
const downloadLocation = downloadLocationer()
|
|
|
|
|
2024-03-14 11:50:46 -04:00
|
|
|
await download.saveAs(downloadLocation)
|
2024-02-27 09:41:09 +11:00
|
|
|
|
|
|
|
if (output.type === 'step') {
|
|
|
|
// stable timestamps for step files
|
|
|
|
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
|
|
|
const newFileContents = fileContents.replace(
|
|
|
|
/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
|
|
|
|
'1970-01-01T00:00:00.0+00:00'
|
|
|
|
)
|
|
|
|
await fsp.writeFile(downloadLocation, newFileContents)
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
modelPath: downloadLocation,
|
|
|
|
imagePath: downloadLocationer('', true),
|
|
|
|
outputType: output.type,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
|
|
|
axis: 'z',
|
|
|
|
direction: 'positive',
|
|
|
|
}
|
|
|
|
const sysType: Models['System_type'] = {
|
|
|
|
forward: axisDirectionPair,
|
|
|
|
up: axisDirectionPair,
|
|
|
|
}
|
|
|
|
|
|
|
|
const exportLocations: Paths[] = []
|
|
|
|
|
|
|
|
// NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input
|
|
|
|
// just note that only `type` and `storage` are used for selecting the drop downs is the app
|
|
|
|
// the rest are only there to make typescript happy
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'step',
|
|
|
|
coords: sysType,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'ply',
|
|
|
|
coords: sysType,
|
|
|
|
selection: { type: 'default_scene' },
|
|
|
|
storage: 'ascii',
|
|
|
|
units: 'in',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'ply',
|
|
|
|
storage: 'binary_little_endian',
|
|
|
|
coords: sysType,
|
|
|
|
selection: { type: 'default_scene' },
|
|
|
|
units: 'in',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'ply',
|
|
|
|
storage: 'binary_big_endian',
|
|
|
|
coords: sysType,
|
|
|
|
selection: { type: 'default_scene' },
|
|
|
|
units: 'in',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'stl',
|
|
|
|
storage: 'ascii',
|
|
|
|
coords: sysType,
|
|
|
|
units: 'in',
|
|
|
|
selection: { type: 'default_scene' },
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'stl',
|
|
|
|
storage: 'binary',
|
|
|
|
coords: sysType,
|
|
|
|
units: 'in',
|
|
|
|
selection: { type: 'default_scene' },
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
// obj seems to be a little flaky, times out tests sometimes
|
|
|
|
type: 'obj',
|
|
|
|
coords: sysType,
|
|
|
|
units: 'in',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'gltf',
|
|
|
|
storage: 'embedded',
|
|
|
|
presentation: 'pretty',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'gltf',
|
|
|
|
storage: 'binary',
|
|
|
|
presentation: 'pretty',
|
|
|
|
})
|
|
|
|
)
|
2024-03-14 11:50:46 -04:00
|
|
|
exportLocations.push(
|
|
|
|
await doExport({
|
|
|
|
type: 'gltf',
|
|
|
|
storage: 'standard',
|
|
|
|
presentation: 'pretty',
|
|
|
|
})
|
|
|
|
)
|
2024-02-27 09:41:09 +11:00
|
|
|
|
|
|
|
// close page to disconnect websocket since we can only have one open atm
|
|
|
|
await page.close()
|
|
|
|
|
|
|
|
// snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
|
|
|
|
// context: https://github.com/KittyCAD/modeling-app/issues/1222
|
2024-03-14 11:50:46 -04:00
|
|
|
for (let { modelPath, imagePath, outputType } of exportLocations) {
|
|
|
|
// May change depending on the file being dealt with
|
|
|
|
let cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
|
|
|
|
|
|
|
|
const parentPath = path.dirname(modelPath)
|
|
|
|
|
|
|
|
// This is actually a zip file.
|
|
|
|
if (modelPath.includes('gltf-standard.gltf')) {
|
|
|
|
console.log('Extracting files from archive')
|
|
|
|
const readZipFile = fsp.readFile(modelPath)
|
|
|
|
const unzip = (archive: any) =>
|
|
|
|
Object.values(archive.files).map((file: any) => ({
|
|
|
|
name: file.name,
|
|
|
|
promise: file.async('nodebuffer'),
|
|
|
|
}))
|
|
|
|
const writeFiles = (files: any) =>
|
|
|
|
Promise.all(
|
|
|
|
files.map((file: any) =>
|
|
|
|
file.promise.then((data: any) => {
|
|
|
|
console.log(`Writing ${file.name}`)
|
|
|
|
return fsp
|
|
|
|
.writeFile(`${parentPath}/${file.name}`, data)
|
|
|
|
.then(() => file.name)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
const filenames = await readZipFile
|
|
|
|
.then(JSZip.loadAsync)
|
|
|
|
.then(unzip)
|
|
|
|
.then(writeFiles)
|
|
|
|
const gltfFilename = filenames.filter((t: string) =>
|
|
|
|
t.includes('.gltf')
|
|
|
|
)[0]
|
|
|
|
if (!gltfFilename) throw new Error('No output.gltf in this archive')
|
|
|
|
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(cliCommand)
|
|
|
|
|
2024-02-27 09:41:09 +11:00
|
|
|
const child = spawn(cliCommand, { shell: true })
|
2024-02-27 15:19:14 +11:00
|
|
|
const result = await new Promise<string>((resolve, reject) => {
|
2024-02-27 09:41:09 +11:00
|
|
|
child.on('error', (code: any, msg: any) => {
|
|
|
|
console.log('error', code, msg)
|
2024-02-27 15:19:14 +11:00
|
|
|
reject('error')
|
2024-02-27 09:41:09 +11:00
|
|
|
})
|
|
|
|
child.on('exit', (code, msg) => {
|
|
|
|
console.log('exit', code, msg)
|
|
|
|
if (code !== 0) {
|
|
|
|
reject(`exit code ${code} for model ${modelPath}`)
|
|
|
|
} else {
|
2024-02-27 15:19:14 +11:00
|
|
|
resolve('success')
|
2024-02-27 09:41:09 +11:00
|
|
|
}
|
|
|
|
})
|
|
|
|
child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
|
|
|
|
child.stdout.on('data', (data) => console.log(`stdout: ${data}`))
|
|
|
|
})
|
2024-02-27 15:19:14 +11:00
|
|
|
expect(result).toBe('success')
|
|
|
|
if (result === 'success') {
|
|
|
|
console.log(`snapshot taken for ${modelPath}`)
|
|
|
|
} else {
|
|
|
|
console.log(`snapshot failed for ${modelPath}`)
|
|
|
|
}
|
2024-02-27 09:41:09 +11:00
|
|
|
}
|
|
|
|
})
|
2024-02-17 12:19:46 +11:00
|
|
|
|
|
|
|
test('extrude on each default plane should be stable', async ({
|
|
|
|
page,
|
|
|
|
context,
|
|
|
|
}) => {
|
2024-04-03 13:22:56 +11:00
|
|
|
await context.addInitScript(async () => {
|
|
|
|
localStorage.setItem(
|
|
|
|
'SETTINGS_PERSIST_KEY',
|
|
|
|
JSON.stringify({
|
|
|
|
baseUnit: 'in',
|
|
|
|
cameraControls: 'KittyCAD',
|
|
|
|
defaultDirectory: '',
|
|
|
|
defaultProjectName: 'project-$nnn',
|
|
|
|
onboardingStatus: 'dismissed',
|
|
|
|
showDebugPanel: true,
|
|
|
|
textWrapping: 'On',
|
|
|
|
theme: 'dark',
|
|
|
|
unitSystem: 'imperial',
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
2024-02-17 12:19:46 +11:00
|
|
|
const u = getUtils(page)
|
|
|
|
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
2024-03-02 08:20:50 +11:00
|
|
|
|> startProfileAt([7.00, 4.40], %)
|
|
|
|
|> line([6.60, -0.20], %)
|
|
|
|
|> line([2.80, 5.00], %)
|
|
|
|
|> line([-5.60, 4.40], %)
|
|
|
|
|> line([-5.40, -3.80], %)
|
2024-02-17 12:19:46 +11:00
|
|
|
|> close(%)
|
2024-03-02 08:20:50 +11:00
|
|
|
|> extrude(10.00, %)
|
2024-02-17 12:19:46 +11:00
|
|
|
`
|
|
|
|
await context.addInitScript(async (code) => {
|
|
|
|
localStorage.setItem('persistCode', code)
|
|
|
|
}, makeCode('XY'))
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
|
|
|
|
// wait for execution done
|
|
|
|
await u.openDebugPanel()
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.clearAndCloseDebugPanel()
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.waitForTimeout(200)
|
2024-02-17 12:19:46 +11:00
|
|
|
|
|
|
|
const runSnapshotsForOtherPlanes = async (plane = 'XY') => {
|
|
|
|
// clear code
|
|
|
|
await u.removeCurrentCode()
|
|
|
|
// add makeCode('XZ')
|
|
|
|
await page.locator('.cm-content').fill(makeCode(plane))
|
|
|
|
// wait for execution done
|
|
|
|
await u.openDebugPanel()
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.clearAndCloseDebugPanel()
|
|
|
|
|
|
|
|
await page.getByText('Code').click()
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.waitForTimeout(80)
|
2024-02-17 12:19:46 +11:00
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
await page.getByText('Code').click()
|
|
|
|
}
|
2024-04-02 10:29:34 -04:00
|
|
|
await runSnapshotsForOtherPlanes('XY')
|
2024-02-17 12:19:46 +11:00
|
|
|
await runSnapshotsForOtherPlanes('-XY')
|
|
|
|
|
|
|
|
await runSnapshotsForOtherPlanes('XZ')
|
|
|
|
await runSnapshotsForOtherPlanes('-XZ')
|
|
|
|
|
|
|
|
await runSnapshotsForOtherPlanes('YZ')
|
|
|
|
await runSnapshotsForOtherPlanes('-YZ')
|
|
|
|
})
|
2024-02-19 13:18:31 +11:00
|
|
|
|
2024-03-01 06:55:49 +11:00
|
|
|
test('Draft segments should look right', async ({ page, context }) => {
|
2024-02-19 13:18:31 +11:00
|
|
|
const u = getUtils(page)
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await u.openDebugPanel()
|
|
|
|
|
2024-02-26 21:02:33 +11:00
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Start Sketch' })
|
|
|
|
).not.toBeDisabled()
|
2024-02-19 13:18:31 +11:00
|
|
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
|
|
|
|
|
|
|
// click on "Start Sketch" button
|
|
|
|
await u.clearCommandLogs()
|
|
|
|
await u.doAndWaitForImageDiff(
|
|
|
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
|
|
|
200
|
|
|
|
)
|
|
|
|
|
|
|
|
// select a plane
|
|
|
|
await page.mouse.click(700, 200)
|
|
|
|
|
|
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
|
|
`const part001 = startSketchOn('-XZ')`
|
|
|
|
)
|
|
|
|
|
|
|
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
|
|
|
|
|
|
|
const startXPx = 600
|
|
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
2024-03-02 08:20:50 +11:00
|
|
|
|> startProfileAt([9.06, -12.22], %)`)
|
2024-02-19 13:18:31 +11:00
|
|
|
await page.waitForTimeout(100)
|
|
|
|
|
|
|
|
await u.closeDebugPanel()
|
|
|
|
await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10)
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
|
|
await page.waitForTimeout(100)
|
|
|
|
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
2024-03-02 08:20:50 +11:00
|
|
|
|> startProfileAt([9.06, -12.22], %)
|
|
|
|
|> line([9.14, 0], %)`)
|
2024-02-19 13:18:31 +11:00
|
|
|
|
|
|
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
|
|
|
|
|
|
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
|
|
|
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
})
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
test('Client side scene scale should match engine scale - Inch', async ({
|
2024-03-01 06:55:49 +11:00
|
|
|
page,
|
|
|
|
}) => {
|
|
|
|
const u = getUtils(page)
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await u.openDebugPanel()
|
|
|
|
|
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Start Sketch' })
|
|
|
|
).not.toBeDisabled()
|
|
|
|
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
|
|
|
|
|
|
|
// click on "Start Sketch" button
|
|
|
|
await u.clearCommandLogs()
|
|
|
|
await u.doAndWaitForImageDiff(
|
|
|
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
|
|
|
200
|
|
|
|
)
|
|
|
|
|
|
|
|
// select a plane
|
|
|
|
await page.mouse.click(700, 200)
|
|
|
|
|
|
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
|
|
`const part001 = startSketchOn('-XZ')`
|
|
|
|
)
|
|
|
|
|
|
|
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
|
|
|
|
|
|
|
const startXPx = 600
|
|
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
2024-04-02 10:29:34 -04:00
|
|
|
|> startProfileAt([9.06, -12.22], %)`)
|
2024-03-01 06:55:49 +11:00
|
|
|
await page.waitForTimeout(100)
|
|
|
|
|
|
|
|
await u.closeDebugPanel()
|
|
|
|
|
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
|
|
await page.waitForTimeout(100)
|
|
|
|
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
2024-04-02 10:29:34 -04:00
|
|
|
|> startProfileAt([9.06, -12.22], %)
|
|
|
|
|> line([9.14, 0], %)`)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
|
|
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
|
|
await page.waitForTimeout(100)
|
|
|
|
|
|
|
|
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
|
|
|
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
2024-04-02 10:29:34 -04:00
|
|
|
|> startProfileAt([9.06, -12.22], %)
|
|
|
|
|> line([9.14, 0], %)
|
|
|
|
|> tangentialArcTo([27.34, -3.08], %)`)
|
2024-03-02 08:20:50 +11:00
|
|
|
|
|
|
|
// click tangential arc tool again to unequip it
|
|
|
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
|
|
await page.waitForTimeout(100)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
|
|
|
// screen shot should show the sketch
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
|
|
|
|
// exit sketch
|
|
|
|
await u.openAndClearDebugPanel()
|
|
|
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
|
|
|
|
|
|
// wait for execution done
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.clearAndCloseDebugPanel()
|
|
|
|
await page.waitForTimeout(200)
|
|
|
|
|
|
|
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
test.describe('Client side scene scale should match engine scale - Millimeters', () => {
|
|
|
|
const storageState = structuredClone(basicStorageState)
|
|
|
|
storageState.origins[0].localStorage[2].value = TOML.stringify({
|
|
|
|
settings: {
|
|
|
|
...basicSettings,
|
|
|
|
modeling: {
|
|
|
|
...basicSettings.modeling,
|
|
|
|
defaultUnit: 'mm',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
test.use({
|
|
|
|
storageState,
|
2024-03-01 06:55:49 +11:00
|
|
|
})
|
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
test('Millimeters', async ({ page }) => {
|
|
|
|
const u = getUtils(page)
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await u.openDebugPanel()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Start Sketch' })
|
|
|
|
).not.toBeDisabled()
|
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Start Sketch' })
|
|
|
|
).toBeVisible()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
// click on "Start Sketch" button
|
|
|
|
await u.clearCommandLogs()
|
|
|
|
await u.doAndWaitForImageDiff(
|
|
|
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
|
|
|
200
|
|
|
|
)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
// select a plane
|
|
|
|
await page.mouse.click(700, 200)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await expect(page.locator('.cm-content')).toHaveText(
|
|
|
|
`const part001 = startSketchOn('-XZ')`
|
|
|
|
)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
const startXPx = 600
|
|
|
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|
|
|> startProfileAt([230.03, -310.32], %)`)
|
|
|
|
await page.waitForTimeout(100)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await u.closeDebugPanel()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
|
|
await page.waitForTimeout(100)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|
|
|> startProfileAt([230.03, -310.32], %)
|
|
|
|
|> line([232.2, 0], %)`)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
|
|
await page.waitForTimeout(100)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
2024-03-02 08:20:50 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await expect(page.locator('.cm-content'))
|
|
|
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
|
|
|> startProfileAt([230.03, -310.32], %)
|
|
|
|
|> line([232.2, 0], %)
|
|
|
|
|> tangentialArcTo([694.43, -78.12], %)`)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
|
|
await page.waitForTimeout(100)
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
// screen shot should show the sketch
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
// exit sketch
|
|
|
|
await u.openAndClearDebugPanel()
|
|
|
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
2024-04-02 10:29:34 -04:00
|
|
|
// wait for execution done
|
|
|
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
|
|
await u.clearAndCloseDebugPanel()
|
|
|
|
await page.waitForTimeout(200)
|
|
|
|
|
|
|
|
// second screen shot should look almost identical, i.e. scale should be the same.
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
2024-03-01 06:55:49 +11:00
|
|
|
})
|
|
|
|
})
|
2024-03-22 10:23:04 +11:00
|
|
|
|
|
|
|
test('Sketch on face with none z-up', async ({ page, context }) => {
|
|
|
|
const u = getUtils(page)
|
|
|
|
await context.addInitScript(async () => {
|
|
|
|
localStorage.setItem(
|
|
|
|
'persistCode',
|
|
|
|
`const part001 = startSketchOn('-XZ')
|
|
|
|
|> startProfileAt([1.4, 2.47], %)
|
2024-04-02 20:20:42 +11:00
|
|
|
|> line([9.31, 10.55], %, 'seg01')
|
2024-03-22 10:23:04 +11:00
|
|
|
|> line([11.91, -10.42], %)
|
|
|
|
|> close(%)
|
|
|
|
|> extrude(5 + 7, %)
|
|
|
|
const part002 = startSketchOn(part001, 'seg01')
|
|
|
|
|> startProfileAt([-2.89, 1.82], %)
|
|
|
|
|> line([4.68, 3.05], %)
|
2024-04-02 20:20:42 +11:00
|
|
|
|> line([0, -7.79], %, 'seg02')
|
2024-03-22 10:23:04 +11:00
|
|
|
|> close(%)
|
|
|
|
|> extrude(5 + 7, %)
|
|
|
|
`
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
await page.setViewportSize({ width: 1200, height: 500 })
|
|
|
|
await page.goto('/')
|
|
|
|
await u.waitForAuthSkipAppStart()
|
|
|
|
await expect(
|
|
|
|
page.getByRole('button', { name: 'Start Sketch' })
|
|
|
|
).not.toBeDisabled()
|
|
|
|
|
|
|
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
|
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
|
|
|
|
|
|
|
// click at 641, 135
|
|
|
|
await page.mouse.click(641, 135)
|
|
|
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
|
|
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
|
|
|
|
|
|
|
await page.waitForTimeout(300)
|
|
|
|
|
|
|
|
await expect(page).toHaveScreenshot({
|
|
|
|
maxDiffPixels: 100,
|
|
|
|
})
|
|
|
|
})
|