Browse Source

Fixes: Mobile - Improve form fields visuals

Vladimir Sheremet 2 years ago
parent
commit
42f876a0be

+ 20 - 9
.cypress/cypress.config.ts

@@ -1,10 +1,10 @@
 import { defineConfig } from 'cypress'
-import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'
 import { rm } from 'node:fs/promises'
 import { resolve } from 'node:path'
+import { initPlugin as initVisualRegressionPlugin } from '@frsource/cypress-plugin-visual-regression-diff/plugins'
 import pkg from '../package.json'
 
-const isCI = !!process.env.CI
+const isCYCI = !process.env.CY_OPEN
 const root = resolve(__dirname, '..')
 
 // we don't need to optimize graphql and apollo
@@ -17,6 +17,13 @@ export default defineConfig({
   downloadsFolder: '.cypress/downloads',
   screenshotsFolder: '.cypress/screenshots',
   videoCompression: false,
+  env: {
+    CY_CI: isCYCI,
+    pluginVisualRegressionDiffConfig: {
+      threshold: 0.02,
+    },
+    pluginVisualRegressionMaxDiffThreshold: 0.02,
+  },
   component: {
     supportFile: '.cypress/support/index.js',
     setupNodeEvents(on, config) {
@@ -25,7 +32,13 @@ export default defineConfig({
           return rm(results.video)
         }
       })
-      addMatchImageSnapshotPlugin(on, config)
+      initVisualRegressionPlugin(on, config)
+      on('before:browser:launch', (browser, launchOptions) => {
+        if (browser?.family === 'chromium' && browser?.name !== 'electron') {
+          launchOptions.args.push('--force-device-scale-factor=2')
+        }
+        return launchOptions
+      })
     },
     devServer: {
       framework: 'vue',
@@ -39,7 +52,8 @@ export default defineConfig({
           fs: {
             strict: false,
           },
-          hmr: !isCI,
+          hmr: true,
+          ...(isCYCI && { watch: { ignored: ['**/*'] } }),
         },
         optimizeDeps: {
           entries: [
@@ -61,10 +75,7 @@ export default defineConfig({
     viewportHeight: 844,
     fileServerFolder: '..',
     indexHtmlFile: '.cypress/support/component-index.html',
-    specPattern: ['**/frontend/**/*.cy.{js,jsx,ts,tsx}'],
-  },
-  retries: {
-    runMode: 2,
-    openMode: 0,
+    specPattern: ['app/frontend/cypress/**/*.cy.{js,jsx,ts,tsx}'],
   },
+  retries: 0,
 })

+ 1 - 1
.cypress/package.json

@@ -1,9 +1,9 @@
 {
   "private": true,
   "devDependencies": {
+    "@frsource/cypress-plugin-visual-regression-diff": "^2.3.8",
     "@testing-library/cypress": "^8.0.3",
     "cypress": "^10.3.1",
-    "cypress-image-snapshot": "^4.0.1",
     "cypress-real-events": "^1.7.2"
   }
 }

+ 2 - 15
.cypress/support/commands.js

@@ -2,13 +2,11 @@
 
 import '@testing-library/cypress/add-commands'
 import 'cypress-real-events/support'
+import '@frsource/cypress-plugin-visual-regression-diff'
+
 import { configure } from '@testing-library/cypress'
-import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'
 import { mount } from 'cypress/vue'
 
-addMatchImageSnapshotCommand({
-  customSnapshotsDir: 'snapshots',
-})
 configure({ testIdAttribute: 'data-test-id' })
 
 Cypress.Commands.add('mount', mount)
@@ -74,14 +72,3 @@ Cypress.Commands.add(
       ])
   },
 )
-
-Cypress.Commands.overwrite('screenshot', (original, ...args) => {
-  const root = document.querySelector('body')
-
-  root.style.setProperty('font-family', 'Helvetica')
-  const result = original(...args)
-  result.then(() => {
-    root.style.removeProperty('font-family')
-  })
-  return result
-})

+ 10 - 10
.cypress/support/component-index.html

@@ -1,12 +1,12 @@
 <!DOCTYPE html>
-<html>
-    <head>
-        <meta charset="utf-8">
-        <meta http-equiv="X-UA-Compatible" content="IE=edge">
-        <meta name="viewport" content="width=device-width,initial-scale=1.0">
-        <title>Components App</title>
-    </head>
-    <body class="bg-black text-white">
-        <div data-cy-root></div>
-    </body>
+<html dir="ltr" lang="en-gb">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <title>Zammad</title>
+  </head>
+  <body class="bg-black font-sans text-sm text-white antialiased">
+    <div data-cy-root></div>
+  </body>
 </html>

+ 8 - 0
.cypress/support/index.js

@@ -7,3 +7,11 @@ import './commands'
 
 // eslint-disable-next-line no-underscore-dangle
 window.__ = (str) => str
+
+Cypress.Screenshot.defaults({ capture: 'viewport' })
+
+if (Cypress.env('CY_CI')) {
+  Cypress.config('defaultCommandTimeout', 20000)
+}
+
+beforeEach(() => document.fonts.ready)

+ 42 - 7
.cypress/types/commands.ts

@@ -28,13 +28,48 @@ declare global {
         files?: File[]
       }): Chainable<Subject>
       selectText(direction: 'left' | 'right', size: number): Chainable<Subject>
-      matchImageSnapshot(
-        options?: Partial<Loggable & Timeoutable & ScreenshotOptions>,
-      ): Chainable<null>
-      matchImageSnapshot(
-        fileName: string,
-        options?: Partial<Loggable & Timeoutable & ScreenshotOptions>,
-      ): Chainable<null>
+      matchImage(
+        options?: Partial<{
+          // screenshot configuration, passed directly to the the Cypress screenshot method: https://docs.cypress.io/api/cypress-api/screenshot-api#Arguments
+          // default: { }
+          screenshotConfig: Partial<Cypress.ScreenshotOptions>
+          // pixelmatch options, see: https://www.npmjs.com/package/pixelmatch#pixelmatchimg1-img2-output-width-height-options
+          // default: { includeAA: true }
+          diffConfig: Partial<{
+            // Matching threshold, ranges from 0 to 1. Smaller values make the comparison more sensitive. 0.1 by default.
+            threshold: number
+            // If true, disables detecting and ignoring anti-aliased pixels.
+            includeAA: boolean
+            // Blending factor of unchanged pixels in the diff output. Ranges from 0 for pure white to 1 for original brightness. 0.1 by default.
+            alpha: number
+            // The color of anti-aliased pixels in the diff output in [R, G, B] format. [255, 255, 0] by default.
+            aaColor: [number, number, number]
+            // The color of differing pixels in the diff output in [R, G, B] format. [255, 0, 0] by default.
+            diffColor: [number, number, number]
+            // An alternative color to use for dark on light differences to differentiate between "added" and "removed" parts. If not provided, all differing pixels use the color specified by diffColor. null by default.
+            diffColorAlt: [number, number, number] | null
+            // Draw the diff over a transparent background (a mask), rather than over the original image. Will not draw anti-aliased pixels (if detected).
+            diffMask: boolean
+          }>
+          // whether to update images automatically, without making a diff - useful for CI
+          // default: false
+          updateImages: boolean
+          // directory path in which screenshot images will be stored
+          // image visualiser will normalise path separators depending on OS it's being run within, so always use / for nested paths
+          // default: '__image_snapshots__'
+          imagesDir: string
+          // maximum threshold above which the test should fail
+          // default: 0.01
+          maxDiffThreshold: number
+          // forces scale factor to be set as value "1"
+          // helps with screenshots being scaled 2x on high-density screens like Mac Retina
+          // default: true
+          forceDeviceScaleFactor: boolean
+          // title used for naming the image file
+          // default: Cypress.currentTest.titlePath (your test title)
+          title: string
+        }>,
+      ): Chainable<Subject>
       mount: typeof import('cypress/vue')['mount']
     }
   }

+ 37 - 4
.cypress/utils/index.ts

@@ -12,16 +12,49 @@ import '@testing-library/cypress'
 import 'cypress-real-events'
 import '../types/commands'
 
-// TODO
-// @ts-expect-error untyped arguments
-export const mountComponent: typeof mount = (component, options) => {
+import type { FormSchemaField } from '@shared/components/Form/types'
+import { FormKit } from '@formkit/vue'
+import type { mount } from 'cypress/vue'
+import { createMemoryHistory, createRouter } from 'vue-router'
+import type { App } from 'vue'
+
+const router = createRouter({
+  history: createMemoryHistory('/'),
+  routes: [{ path: '/', component: { template: '<div />' } }],
+})
+
+export const mountComponent: typeof mount = (
+  component: any,
+  options: any,
+): Cypress.Chainable => {
   const plugins = []
   plugins.push(initializeStore)
   plugins.push(initializeGlobalComponents)
   plugins.push(initializeGlobalProperties)
   plugins.push(initializeForm)
+  plugins.push((app: App) => router.install(app))
 
   return cy.mount(component, merge({ global: { plugins } }, options))
 }
 
-export default {}
+export const mountFormField = (
+  field: string,
+  props?: Partial<FormSchemaField> & Record<string, unknown>,
+) => {
+  return mountComponent(FormKit, {
+    props: {
+      name: field,
+      type: field,
+      ...props,
+    },
+  })
+}
+
+export const checkFormMatchesSnapshot = (title: string, type = '') => {
+  return cy.wrap(document.fonts.ready).then(() => {
+    cy.get('.formkit-outer').matchImage({
+      title: type ? `${type} - ${title}` : title,
+      imagesDir: type ? `__image_snapshots__/${type}` : undefined,
+    })
+  })
+}

+ 16 - 0
.cypress/visual-regression/README.md

@@ -0,0 +1,16 @@
+# Updating Cypress Visual Snapshots
+
+This command only _updates_ snapshots. Cypress will run only tests with `*-visual.cy` in their name, and will not fail, if snapshot is not correct, but will generate a new snapshot instead. If you are using Mac, it is advised to run this command in a separate project, because we have packages that depend on a platform, but folder structure is shared between docker (Linux machine) and Mac.
+
+```sh
+# from the root directory
+$ yarn cypress:snapshots
+```
+
+Please, make sure snapshot is actually correct before pushing your changes.
+
+## Explanation
+
+Cypress doesn't work inside amd64 image, if it's running on ARM processor, even under `--platform=linux/amd64`. So we have two compose files, and based on user processor we run one or the other.
+
+ARM compose file has a custom cache folder, so it doesn't conflict with Mac's usual `~/Library/Cache` folder. It's meant to store cache inside the project folder, but Linux cache can be theoretically shared.

+ 14 - 0
.cypress/visual-regression/docker-compose.amd64.yml

@@ -0,0 +1,14 @@
+version: '3.6'
+services:
+  cypress:
+    platform: linux/amd64
+    image: cypress/base:16.17.1
+    ipc: host
+    volumes:
+      - ../..:/app
+      - ~/.cache/Cypress:/root/.cache/Cypress
+    working_dir: /app
+    ports:
+      - "8000:8000"
+      - "443:443"
+    command: bash ./.cypress/visual-regression/run.sh

+ 15 - 0
.cypress/visual-regression/docker-compose.arm.yml

@@ -0,0 +1,15 @@
+version: '3.6'
+services:
+  cypress:
+    environment:
+      CYPRESS_CACHE_FOLDER: $PWD/.cache/Cypress
+    platform: linux/arm64
+    image: cypress/base:16.17.1
+    ipc: host
+    volumes:
+      - ../..:/app
+    working_dir: /app
+    ports:
+      - "8000:8000"
+      - "443:443"
+    command: bash ./.cypress/visual-regression/run.sh

Some files were not shown because too many files changed in this diff