From 9b173b03ac106198bb8fce75da13c9e9172eeda5 Mon Sep 17 00:00:00 2001 From: Alex Plischke Date: Mon, 15 Jul 2024 15:00:55 -0700 Subject: [PATCH] feat!: drop support for cypress v1alpha (#925) --- api/global.schema.json | 15 - api/saucectl.schema.json | 859 ++++++++-------------- api/v1alpha/framework/cypress.schema.json | 199 ----- internal/cmd/ini/cypress.go | 44 -- internal/cypress/config.go | 7 +- internal/cypress/v1alpha/config.go | 633 ---------------- internal/cypress/v1alpha/config_test.go | 531 ------------- internal/cypress/v1alpha/native.go | 39 - internal/cypress/v1alpha/native_test.go | 55 -- 9 files changed, 328 insertions(+), 2054 deletions(-) delete mode 100644 api/v1alpha/framework/cypress.schema.json delete mode 100644 internal/cypress/v1alpha/config.go delete mode 100644 internal/cypress/v1alpha/config_test.go delete mode 100644 internal/cypress/v1alpha/native.go delete mode 100644 internal/cypress/v1alpha/native_test.go diff --git a/api/global.schema.json b/api/global.schema.json index c0e5bcf22..dba497cac 100644 --- a/api/global.schema.json +++ b/api/global.schema.json @@ -19,21 +19,6 @@ } }, "allOf": [ - { - "if": { - "properties": { - "kind": { - "const": "cypress" - }, - "apiVersion": { - "const": "v1alpha" - } - } - }, - "then": { - "$ref": "v1alpha/framework/cypress.schema.json" - } - }, { "if": { "properties": { diff --git a/api/saucectl.schema.json b/api/saucectl.schema.json index f28c2be20..c946998cf 100644 --- a/api/saucectl.schema.json +++ b/api/saucectl.schema.json @@ -26,7 +26,7 @@ "const": "cypress" }, "apiVersion": { - "const": "v1alpha" + "const": "v1" } } }, @@ -200,7 +200,6 @@ "description": "Which Sauce Labs data center to target.", "enum": [ "us-west-1", - "us-east-4", "eu-central-1" ] }, @@ -218,15 +217,6 @@ "owner": { "description": "The owner (username) of the tunnel. Must be specified if the user that created the tunnel differs from the user that is running the tests.", "type": "string" - }, - "timeout": { - "description": "How long to wait for the specified tunnel to be ready. Supports duration values like '10s', '30m' etc.", - "type": "string", - "pattern": "^(?:\\d+h)?(?:\\d+m)?(?:\\d+s)?(?:\\d+ms)?$", - "examples": [ - "1m", - "30s" - ] } }, "required": [ @@ -344,7 +334,7 @@ ], "properties": { "apiVersion": { - "const": "v1alpha" + "const": "v1" }, "kind": { "const": "cypress" @@ -358,7 +348,14 @@ "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "description": "Instructs how long (in ms, s, m, or h) saucectl should wait for a suite to complete.", + "type": "string", + "pattern": "^(?:\\d+h)?(?:\\d+m)?(?:\\d+s)?(?:\\d+ms)?$", + "examples": [ + "1h", + "10m", + "90s" + ] } }, "additionalProperties": false @@ -372,10 +369,10 @@ "type": "object", "properties": { "configFile": { - "description": "The designated cypress configuration file. SauceCTL determines related files based on the location of the config file. By default saucectl defers to the test file location defined in cypress.json.", + "description": "The designated cypress configuration file.", "type": "string", "examples": [ - "cypress.json" + "cypress.config.js" ] }, "key": { @@ -387,11 +384,20 @@ "type": "boolean" }, "version": { - "$ref": "#/allOf/8/then/properties/playwright/properties/version", "enum": [ "package.json", - "9.7.0" - ] + "13.12.0", + "13.10.0", + "13.7.3", + "13.6.6", + "13.6.3", + "13.6.0", + "13.4.0", + "13.3.0", + "12.17.4", + "12.17.2" + ], + "description": "Which framework version to use." }, "reporters": { "description": "Set of reporter to use.", @@ -438,26 +444,27 @@ "type": "string" }, "browser": { - "$ref": "#/allOf/8/then/properties/suites/items/properties/browserName", "enum": [ "chrome", "firefox", - "microsoftedge" - ] + "microsoftedge", + "webkit" + ], + "description": "The name of the browser in which to run the tests." }, "browserVersion": { "description": "Which version of the browser to use.", "type": "string" }, "platformName": { - "$ref": "#/allOf/4/then/properties/suites/items/properties/platform", "enum": [ "macOS 11.00", "macOS 12", "macOS 13", "Windows 10", "Windows 11" - ] + ], + "description": "A specific operating system on which to run the tests. Sauce Labs will try to choose a reasonable default if not explicitly specified." }, "screenResolution": { "description": "Specifies a browser window screen resolution, which may be useful if you are attempting to simulate a browser on a particular device type.", @@ -467,19 +474,28 @@ "description": "Provides details related to the Cypress test configuration.", "type": "object", "properties": { - "testFiles": { - "description": "One or more paths to the Cypress test files to run for this suite, if not otherwise specified explicitly in cypress.json.", + "testingType": { + "description": "Specify the type of tests to execute; either e2e or component. Defaults to e2e", + "enum": [ + "e2e", + "component" + ], + "default": "e2e" + }, + "specPattern": { + "description": "One or more paths to the Cypress test files to run for this suite.", "type": "array" }, - "excludedTestFiles": { - "$ref": "#/allOf/3/then/properties/suites/items/properties/excludedTestFiles" + "excludeSpecPattern": { + "description": "Exclude test files for this suite.", + "type": "array" }, "env": { "$ref": "#/allOf/0/then/properties/env" } }, "required": [ - "testFiles" + "specPattern" ] }, "shard": { @@ -490,12 +506,16 @@ "spec" ] }, + "shardGrepEnabled": { + "description": "When sharding is configured and the suite is configured to filter using cypress-grep, let saucectl filter tests before executing.", + "type": "boolean" + }, "headless": { "description": "Controls whether or not tests are run in headless mode (default: false)", "type": "boolean" }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/0/then/properties/defaults/properties/timeout" }, "preExec": { "description": "Specifies which commands to execute before starting the tests.", @@ -544,149 +564,16 @@ "if": { "properties": { "kind": { - "const": "cypress" - }, - "apiVersion": { - "const": "v1" + "const": "espresso" } } }, "then": { "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl cypress runner configuration", - "description": "Configuration file for cypress using saucectl", + "title": "saucectl espresso runner configuration", + "description": "Configuration file for espresso using saucectl", "type": "object", "allOf": [ - { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl artifacts schema", - "description": "Subschema for controlling artifacts", - "type": "object", - "properties": { - "artifacts": { - "description": "Manage test output, such as logs, videos, and screenshots.", - "type": "object", - "properties": { - "cleanup": { - "description": "Whether to remove all contents of artifacts directory", - "type": "boolean" - }, - "download": { - "description": "Settings related to downloading test artifacts from Sauce Labs.", - "type": "object", - "properties": { - "match": { - "description": "Specifies which artifacts to download based on whether they match the file pattern provided. Supports the wildcard character '*'.", - "type": "array" - }, - "when": { - "description": "Specifies when and under what circumstances to download artifacts.", - "enum": [ - "always", - "fail", - "never", - "pass" - ] - }, - "directory": { - "description": "Specifies the path to the folder in which to download artifacts. A separate subdirectory is generated in this location for each suite.", - "type": "string" - } - }, - "required": [ - "when", - "match", - "directory" - ], - "additionalProperties": false - }, - "retain": { - "description": "Compress folders into zip files, which can then be downloaded as artifacts.", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": true - }, - { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl npm specific schema", - "description": "Subschema for npm specific settings", - "type": "object", - "properties": { - "npm": { - "description": "Settings specific to npm.", - "type": "object", - "properties": { - "packages": { - "description": "Specifies any npm packages that are required to run tests.", - "type": "object" - }, - "dependencies": { - "description": "Specify local npm dependencies for saucectl to upload. These dependencies must already be installed in the local node_modules directory.", - "type": "array" - }, - "registry": { - "description": "Override the default and official NPM registry URL with a custom one.", - "type": "string", - "deprecated": true - }, - "strictSSL": { - "description": "Whether or not to do SSL key validation when making requests to the registry via https.", - "type": "boolean" - }, - "registries": { - "description": "Specify all the registries you want to configure", - "type": "array", - "minimum": 0, - "items": { - "type": "object", - "properties": { - "scope": { - "description": "Scope for the registry entry", - "type": "string" - }, - "url": { - "description": "URL for the registry entry", - "type": "string" - }, - "authToken": { - "description": "Authentication token for the registry entry", - "type": "string" - }, - "auth": { - "description": "Base64-encoded authentication string for the registry entry", - "type": "string" - }, - "username": { - "description": "Username for authentication with the registry", - "type": "string" - }, - "password": { - "description": "Password for authentication with the registry", - "type": "string" - }, - "email": { - "description": "Email for authentication with the registry", - "type": "string" - } - }, - "required": [ - "url" - ] - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": true - }, { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "saucectl sauce specific schema", @@ -721,6 +608,7 @@ "description": "Which Sauce Labs data center to target.", "enum": [ "us-west-1", + "us-east-4", "eu-central-1" ] }, @@ -738,6 +626,15 @@ "owner": { "description": "The owner (username) of the tunnel. Must be specified if the user that created the tunnel differs from the user that is running the tests.", "type": "string" + }, + "timeout": { + "description": "How long to wait for the specified tunnel to be ready. Supports duration values like '10s', '30m' etc.", + "type": "string", + "pattern": "^(?:\\d+h)?(?:\\d+m)?(?:\\d+s)?(?:\\d+ms)?$", + "examples": [ + "1m", + "30s" + ] } }, "required": [ @@ -795,55 +692,52 @@ }, { "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl reporters specific schema", - "description": "Subschema for reporters specific settings", + "title": "saucectl artifacts schema", + "description": "Subschema for controlling artifacts", "type": "object", "properties": { - "reporters": { + "artifacts": { + "description": "Manage test output, such as logs, videos, and screenshots.", "type": "object", "properties": { - "junit": { - "type": "object", - "description": "The JUnit reporter merges test results from all jobs in the JUnit format into a single report.", - "properties": { - "enabled": { - "description": "Toggles the reporter on/off.", - "type": "boolean" - }, - "filename": { - "description": "Filename for the generated JUnit report.", - "type": "string", - "default": "saucectl-report.xml" - } - } + "cleanup": { + "description": "Whether to remove all contents of artifacts directory", + "type": "boolean" }, - "json": { + "download": { + "description": "Settings related to downloading test artifacts from Sauce Labs.", "type": "object", - "description": "The JSON reporter merges test results from all jobs in the JSON format into a single report.", "properties": { - "enabled": { - "description": "Toggles the reporter on/off.", - "type": "boolean" - }, - "webhookURL": { - "description": "Webhook URL to pass JSON report.", - "type": "string" + "match": { + "description": "Specifies which artifacts to download based on whether they match the file pattern provided. Supports the wildcard character '*'.", + "type": "array" }, - "filename": { - "description": "Filename for the generated JSON report.", - "type": "string", - "default": "saucectl-report.json" - } - } + "when": { + "description": "Specifies when and under what circumstances to download artifacts.", + "enum": [ + "always", + "fail", + "never", + "pass" + ] + }, + "directory": { + "description": "Specifies the path to the folder in which to download artifacts. A separate subdirectory is generated in this location for each suite.", + "type": "string" + } + }, + "required": [ + "when", + "match", + "directory" + ], + "additionalProperties": false }, - "spotlight": { + "retain": { + "description": "Compress folders into zip files, which can then be downloaded as artifacts.", "type": "object", - "description": "The spotlight reporter prints an overview of failed, or otherwise interesting, jobs.", - "properties": { - "enabled": { - "description": "Toggles the reporter on/off.", - "type": "boolean" - } + "additionalProperties": { + "type": "string" } } }, @@ -851,258 +745,65 @@ } }, "additionalProperties": true - } - ], - "properties": { - "apiVersion": { - "const": "v1" - }, - "kind": { - "const": "cypress" - }, - "showConsoleLog": { - "description": "Shows suites console.log locally. By default console.log is only shown on failures.", - "type": "boolean" - }, - "defaults": { - "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", - "type": "object", - "properties": { - "timeout": { - "description": "Instructs how long (in ms, s, m, or h) saucectl should wait for a suite to complete.", - "type": "string", - "pattern": "^(?:\\d+h)?(?:\\d+m)?(?:\\d+s)?(?:\\d+ms)?$", - "examples": [ - "1h", - "10m", - "90s" - ] - } - }, - "additionalProperties": false - }, - "rootDir": { - "description": "The directory of files that need to be bundled and uploaded for the tests to run.", - "type": "string" }, - "cypress": { - "description": "Contains details specific to the Cypress project.", + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "saucectl reporters specific schema", + "description": "Subschema for reporters specific settings", "type": "object", "properties": { - "configFile": { - "description": "The designated cypress configuration file.", - "type": "string", - "examples": [ - "cypress.config.js" - ] - }, - "key": { - "description": "The secret key that grants permission to record your tests in the Cypress dashboard.", - "type": "string" - }, - "record": { - "description": "Whether to record your test results in the Cypress dashboard.", - "type": "boolean" - }, - "version": { - "enum": [ - "package.json", - "13.12.0", - "13.10.0", - "13.7.3", - "13.6.6", - "13.6.3", - "13.6.0", - "13.4.0", - "13.3.0", - "12.17.4", - "12.17.2" - ], - "description": "Which framework version to use." - }, "reporters": { - "description": "Set of reporter to use.", - "type": "array", - "minimum": 0, - "items": { - "type": "object", - "properties": { - "name": { - "description": "Name of the reporter. You may have to install the associated npm dependency through npm.packages field.", - "type": "string", - "examples": [ - "dot", - "nyan" - ] - }, - "options": { - "description": "Options to pass to the reporter." + "type": "object", + "properties": { + "junit": { + "type": "object", + "description": "The JUnit reporter merges test results from all jobs in the JUnit format into a single report.", + "properties": { + "enabled": { + "description": "Toggles the reporter on/off.", + "type": "boolean" + }, + "filename": { + "description": "Filename for the generated JUnit report.", + "type": "string", + "default": "saucectl-report.xml" + } } }, - "additionalProperties": false - } - } - }, - "required": [ - "configFile", - "version" - ], - "additionalProperties": false - }, - "env": { - "description": "Set one or more environment variables. Values can be environment variables themselves. Not supported when running espresso/xcuitest!", - "type": "object" - }, - "suites": { - "description": "The set of properties providing details about the test suites to run.", - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "name": { - "description": "The name of the test suite, which will be reflected in the test results in Sauce Labs.", - "type": "string" - }, - "browser": { - "enum": [ - "chrome", - "firefox", - "microsoftedge", - "webkit" - ], - "description": "The name of the browser in which to run the tests." - }, - "browserVersion": { - "description": "Which version of the browser to use.", - "type": "string" - }, - "platformName": { - "enum": [ - "macOS 11.00", - "macOS 12", - "macOS 13", - "Windows 10", - "Windows 11" - ], - "description": "A specific operating system on which to run the tests. Sauce Labs will try to choose a reasonable default if not explicitly specified." - }, - "screenResolution": { - "description": "Specifies a browser window screen resolution, which may be useful if you are attempting to simulate a browser on a particular device type.", - "type": "string" - }, - "config": { - "description": "Provides details related to the Cypress test configuration.", - "type": "object", - "properties": { - "testingType": { - "description": "Specify the type of tests to execute; either e2e or component. Defaults to e2e", - "enum": [ - "e2e", - "component" - ], - "default": "e2e" - }, - "specPattern": { - "description": "One or more paths to the Cypress test files to run for this suite.", - "type": "array" - }, - "excludeSpecPattern": { - "description": "Exclude test files for this suite.", - "type": "array" - }, - "env": { - "$ref": "#/allOf/1/then/properties/env" + "json": { + "type": "object", + "description": "The JSON reporter merges test results from all jobs in the JSON format into a single report.", + "properties": { + "enabled": { + "description": "Toggles the reporter on/off.", + "type": "boolean" + }, + "webhookURL": { + "description": "Webhook URL to pass JSON report.", + "type": "string" + }, + "filename": { + "description": "Filename for the generated JSON report.", + "type": "string", + "default": "saucectl-report.json" + } } }, - "required": [ - "specPattern" - ] - }, - "shard": { - "description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec or concurrency) so that they can easily run in parallel.", - "enum": [ - "", - "concurrency", - "spec" - ] - }, - "shardGrepEnabled": { - "description": "When sharding is configured and the suite is configured to filter using cypress-grep, let saucectl filter tests before executing.", - "type": "boolean" - }, - "headless": { - "description": "Controls whether or not tests are run in headless mode (default: false)", - "type": "boolean" - }, - "timeout": { - "$ref": "#/allOf/1/then/properties/defaults/properties/timeout" - }, - "preExec": { - "description": "Specifies which commands to execute before starting the tests.", - "type": "array" - }, - "timeZone": { - "description": "Specifies the timeZone for the suite.", - "type": "string" - }, - "passThreshold": { - "description": "The minimum number of successful attempts for a suite to be considered as 'passed'.", - "type": "integer", - "minimum": 1 - }, - "smartRetry": { - "description": "Optimize suite retries by configuring the strategy.", - "type": "object", - "properties": { - "failedOnly": { - "description": "Optimize suite retries by retrying failed tests, classes or spec files only.", - "type": "boolean", - "default": false + "spotlight": { + "type": "object", + "description": "The spotlight reporter prints an overview of failed, or otherwise interesting, jobs.", + "properties": { + "enabled": { + "description": "Toggles the reporter on/off.", + "type": "boolean" + } } } - } - }, - "required": [ - "name", - "browser", - "config" - ], - "additionalProperties": false - } - } - }, - "required": [ - "apiVersion", - "kind", - "cypress", - "suites" - ], - "additionalProperties": true - } - }, - { - "if": { - "properties": { - "kind": { - "const": "espresso" - } - } - }, - "then": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl espresso runner configuration", - "description": "Configuration file for espresso using saucectl", - "type": "object", - "allOf": [ - { - "$ref": "#/allOf/0/then/allOf/2" - }, - { - "$ref": "#/allOf/0/then/allOf/0" - }, - { - "$ref": "#/allOf/0/then/allOf/3" + }, + "additionalProperties": false + } + }, + "additionalProperties": true } ], "properties": { @@ -1113,14 +814,15 @@ "const": "espresso" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "description": "Shows suites console.log locally. By default console.log is only shown on failures.", + "type": "boolean" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false @@ -1373,7 +1075,7 @@ } }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "appSettings": { "description": "Overwrite real device settings.", @@ -1396,10 +1098,20 @@ } }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "description": "The minimum number of successful attempts for a suite to be considered as 'passed'.", + "type": "integer", + "minimum": 1 }, "smartRetry": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/smartRetry" + "description": "Optimize suite retries by configuring the strategy.", + "type": "object", + "properties": { + "failedOnly": { + "description": "Optimize suite retries by retrying failed tests, classes or spec files only.", + "type": "boolean", + "default": false + } + } } }, "anyOf": [ @@ -1445,16 +1157,87 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/0" + "$ref": "#/allOf/1/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/1" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "saucectl npm specific schema", + "description": "Subschema for npm specific settings", + "type": "object", + "properties": { + "npm": { + "description": "Settings specific to npm.", + "type": "object", + "properties": { + "packages": { + "description": "Specifies any npm packages that are required to run tests.", + "type": "object" + }, + "dependencies": { + "description": "Specify local npm dependencies for saucectl to upload. These dependencies must already be installed in the local node_modules directory.", + "type": "array" + }, + "registry": { + "description": "Override the default and official NPM registry URL with a custom one.", + "type": "string", + "deprecated": true + }, + "strictSSL": { + "description": "Whether or not to do SSL key validation when making requests to the registry via https.", + "type": "boolean" + }, + "registries": { + "description": "Specify all the registries you want to configure", + "type": "array", + "minimum": 0, + "items": { + "type": "object", + "properties": { + "scope": { + "description": "Scope for the registry entry", + "type": "string" + }, + "url": { + "description": "URL for the registry entry", + "type": "string" + }, + "authToken": { + "description": "Authentication token for the registry entry", + "type": "string" + }, + "auth": { + "description": "Base64-encoded authentication string for the registry entry", + "type": "string" + }, + "username": { + "description": "Username for authentication with the registry", + "type": "string" + }, + "password": { + "description": "Password for authentication with the registry", + "type": "string" + }, + "email": { + "description": "Email for authentication with the registry", + "type": "string" + } + }, + "required": [ + "url" + ] + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": true }, { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" }, { - "$ref": "#/allOf/0/then/allOf/3" + "$ref": "#/allOf/1/then/allOf/2" } ], "properties": { @@ -1465,30 +1248,32 @@ "const": "playwright" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "$ref": "#/allOf/1/then/properties/showConsoleLog" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "description": "Set one or more environment variables. Values can be environment variables themselves. Not supported when running espresso/xcuitest!", + "type": "object" }, "rootDir": { - "$ref": "#/allOf/0/then/properties/rootDir" + "description": "The directory of files that need to be bundled and uploaded for the tests to run.", + "type": "string" }, "playwright": { "description": "Contains details specific to the Playwright project.", "type": "object", "properties": { "version": { - "$ref": "#/allOf/8/then/properties/playwright/properties/version", + "$ref": "#/allOf/7/then/properties/playwright/properties/version", "enum": [ "package.json", "1.45.0", @@ -1525,7 +1310,7 @@ "type": "string" }, "playwrightVersion": { - "$ref": "#/allOf/8/then/properties/playwright/properties/version" + "$ref": "#/allOf/7/then/properties/playwright/properties/version" }, "testMatch": { "description": "Paths to the playwright test files. Regex values are supported to indicate all files of a certain type or in a certain directory, etc.", @@ -1536,7 +1321,7 @@ "type": "array" }, "platformName": { - "$ref": "#/allOf/4/then/properties/suites/items/properties/platform", + "$ref": "#/allOf/3/then/properties/suites/items/properties/platform", "enum": [ "macOS 11.00", "macOS 12", @@ -1550,7 +1335,7 @@ "type": "object", "properties": { "browserName": { - "$ref": "#/allOf/8/then/properties/suites/items/properties/browserName", + "$ref": "#/allOf/7/then/properties/suites/items/properties/browserName", "enum": [ "chromium", "firefox", @@ -1612,10 +1397,11 @@ ] }, "screenResolution": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/screenResolution" + "description": "Specifies a browser window screen resolution, which may be useful if you are attempting to simulate a browser on a particular device type.", + "type": "string" }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "numShards": { "description": "When sharding is configured, saucectl automatically creates the sharded jobs based on the number of shards you specify. For example, for a suite that specifies 2 shards, saucectl clones the suite and runs shard 1/2 on the first suite, and the other shard 2/2 on the identical clone suite.", @@ -1635,19 +1421,21 @@ "type": "boolean" }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "preExec": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/preExec" + "description": "Specifies which commands to execute before starting the tests.", + "type": "array" }, "timeZone": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/timeZone" + "description": "Specifies the timeZone for the suite.", + "type": "string" }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "$ref": "#/allOf/1/then/properties/suites/items/properties/passThreshold" }, "smartRetry": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/smartRetry" + "$ref": "#/allOf/1/then/properties/suites/items/properties/smartRetry" } }, "required": [ @@ -1683,10 +1471,10 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/0" + "$ref": "#/allOf/1/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" } ], "properties": { @@ -1697,14 +1485,14 @@ "const": "puppeteer-replay" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "$ref": "#/allOf/1/then/properties/showConsoleLog" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false @@ -1725,13 +1513,14 @@ "type": "array" }, "browserName": { - "$ref": "#/allOf/8/then/properties/suites/items/properties/browserName", + "$ref": "#/allOf/7/then/properties/suites/items/properties/browserName", "enum": [ "chrome" ] }, "browserVersion": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/browserVersion" + "description": "Which version of the browser to use.", + "type": "string" }, "platform": { "enum": [ @@ -1744,10 +1533,10 @@ "description": "A specific operating system on which to run the tests. Sauce Labs will try to choose a reasonable default if not explicitly specified." }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "$ref": "#/allOf/1/then/properties/suites/items/properties/passThreshold" } }, "required": [ @@ -1781,16 +1570,16 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/0" + "$ref": "#/allOf/1/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/1" + "$ref": "#/allOf/2/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" }, { - "$ref": "#/allOf/0/then/allOf/3" + "$ref": "#/allOf/1/then/allOf/2" } ], "properties": { @@ -1801,30 +1590,30 @@ "const": "testcafe" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "$ref": "#/allOf/1/then/properties/showConsoleLog" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "rootDir": { - "$ref": "#/allOf/0/then/properties/rootDir" + "$ref": "#/allOf/2/then/properties/rootDir" }, "testcafe": { "description": "Contains details specific to the TestCafe project.", "type": "object", "properties": { "version": { - "$ref": "#/allOf/8/then/properties/playwright/properties/version", + "$ref": "#/allOf/7/then/properties/playwright/properties/version", "enum": [ "package.json", "3.6.1", @@ -1863,7 +1652,7 @@ "type": "string" }, "browserName": { - "$ref": "#/allOf/8/then/properties/suites/items/properties/browserName", + "$ref": "#/allOf/7/then/properties/suites/items/properties/browserName", "enum": [ "chrome", "firefox", @@ -1872,7 +1661,7 @@ ] }, "browserVersion": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/browserVersion" + "$ref": "#/allOf/3/then/properties/suites/items/properties/browserVersion" }, "browserArgs": { "description": "Browser specific arguments.", @@ -1891,7 +1680,7 @@ "type": "boolean" }, "platformName": { - "$ref": "#/allOf/4/then/properties/suites/items/properties/platform", + "$ref": "#/allOf/3/then/properties/suites/items/properties/platform", "enum": [ "macOS 11.00", "macOS 12", @@ -1910,7 +1699,7 @@ "type": "array" }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "shard": { "description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec or concurrency) so that they can easily run in parallel.", @@ -2009,7 +1798,7 @@ } }, "screenResolution": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/screenResolution" + "$ref": "#/allOf/2/then/properties/suites/items/properties/screenResolution" }, "screenshots": { "description": "Allows you to specify the screenshot options.", @@ -2069,7 +1858,7 @@ ] }, "orientation": { - "$ref": "#/allOf/2/then/properties/suites/items/properties/emulators/items/properties/orientation" + "$ref": "#/allOf/1/then/properties/suites/items/properties/emulators/items/properties/orientation" }, "platformName": { "description": "The name of the simulator platform.", @@ -2108,22 +1897,22 @@ "type": "boolean" }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "preExec": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/preExec" + "$ref": "#/allOf/2/then/properties/suites/items/properties/preExec" }, "excludedTestFiles": { - "$ref": "#/allOf/3/then/properties/suites/items/properties/excludedTestFiles" + "$ref": "#/allOf/2/then/properties/suites/items/properties/excludedTestFiles" }, "timeZone": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/timeZone" + "$ref": "#/allOf/2/then/properties/suites/items/properties/timeZone" }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "$ref": "#/allOf/1/then/properties/suites/items/properties/passThreshold" }, "smartRetry": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/smartRetry" + "$ref": "#/allOf/1/then/properties/suites/items/properties/smartRetry" } }, "required": [ @@ -2159,13 +1948,13 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" }, { - "$ref": "#/allOf/0/then/allOf/0" + "$ref": "#/allOf/1/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/3" + "$ref": "#/allOf/1/then/allOf/2" } ], "properties": { @@ -2176,20 +1965,20 @@ "const": "xcuitest" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "$ref": "#/allOf/1/then/properties/showConsoleLog" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "xcuitest": { "description": "Contains details specific to the XCUITest project.", @@ -2250,7 +2039,7 @@ "type": "array" }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "testOptions": { "description": "Allows you to control various details on how tests are executed.", @@ -2296,7 +2085,7 @@ "additionalProperties": false }, "appSettings": { - "$ref": "#/allOf/2/then/properties/suites/items/properties/appSettings" + "$ref": "#/allOf/1/then/properties/suites/items/properties/appSettings" }, "simulators": { "description": "Defines details for running this suite on virtual devices using a simulator.", @@ -2308,7 +2097,7 @@ "type": "string" }, "orientation": { - "$ref": "#/allOf/2/then/properties/suites/items/properties/emulators/items/properties/orientation" + "$ref": "#/allOf/1/then/properties/suites/items/properties/emulators/items/properties/orientation" }, "platformVersions": { "description": "The set of one or more versions of the device platform on which to run the test suite.", @@ -2392,13 +2181,13 @@ } }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "$ref": "#/allOf/1/then/properties/suites/items/properties/passThreshold" }, "smartRetry": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/smartRetry" + "$ref": "#/allOf/1/then/properties/suites/items/properties/smartRetry" }, "shard": { "description": "When shard is configured as concurrency, saucectl automatically splits the tests by concurrency so that they can easily run in parallel.", @@ -2454,7 +2243,7 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" } ], "properties": { @@ -2527,16 +2316,16 @@ "type": "object", "allOf": [ { - "$ref": "#/allOf/0/then/allOf/0" + "$ref": "#/allOf/1/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/1" + "$ref": "#/allOf/2/then/allOf/1" }, { - "$ref": "#/allOf/0/then/allOf/2" + "$ref": "#/allOf/1/then/allOf/0" }, { - "$ref": "#/allOf/0/then/allOf/3" + "$ref": "#/allOf/1/then/allOf/2" } ], "properties": { @@ -2547,20 +2336,20 @@ "const": "playwright-cucumberjs" }, "showConsoleLog": { - "$ref": "#/allOf/0/then/properties/showConsoleLog" + "$ref": "#/allOf/1/then/properties/showConsoleLog" }, "defaults": { "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", "type": "object", "properties": { "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" } }, "additionalProperties": false }, "rootDir": { - "$ref": "#/allOf/0/then/properties/rootDir" + "$ref": "#/allOf/2/then/properties/rootDir" }, "playwright": { "description": "Contains details specific to the playwright.", @@ -2576,7 +2365,7 @@ "additionalProperties": false }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "suites": { "description": "The set of properties providing details about the test suites to run.", @@ -2593,10 +2382,10 @@ "description": "The name of the browser in which to run the tests." }, "browserVersion": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/browserVersion" + "$ref": "#/allOf/3/then/properties/suites/items/properties/browserVersion" }, "platformName": { - "$ref": "#/allOf/4/then/properties/suites/items/properties/platform", + "$ref": "#/allOf/3/then/properties/suites/items/properties/platform", "enum": [ "macOS 11.00", "macOS 12", @@ -2606,10 +2395,10 @@ ] }, "screenResolution": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/screenResolution" + "$ref": "#/allOf/2/then/properties/suites/items/properties/screenResolution" }, "env": { - "$ref": "#/allOf/0/then/properties/env" + "$ref": "#/allOf/2/then/properties/env" }, "options": { "description": "Provides details related to the Cucumberjs test configuration.", @@ -2628,7 +2417,7 @@ "type": "array" }, "excludedTestFiles": { - "$ref": "#/allOf/3/then/properties/suites/items/properties/excludedTestFiles" + "$ref": "#/allOf/2/then/properties/suites/items/properties/excludedTestFiles" }, "backtrace": { "description": "Show the full backtrace for errors.", @@ -2672,19 +2461,19 @@ ] }, "timeout": { - "$ref": "#/allOf/9/then/definitions/suite/properties/timeout" + "$ref": "#/allOf/8/then/definitions/suite/properties/timeout" }, "preExec": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/preExec" + "$ref": "#/allOf/2/then/properties/suites/items/properties/preExec" }, "timeZone": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/timeZone" + "$ref": "#/allOf/2/then/properties/suites/items/properties/timeZone" }, "passThreshold": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/passThreshold" + "$ref": "#/allOf/1/then/properties/suites/items/properties/passThreshold" }, "smartRetry": { - "$ref": "#/allOf/0/then/properties/suites/items/properties/smartRetry" + "$ref": "#/allOf/1/then/properties/suites/items/properties/smartRetry" } }, "required": [ @@ -2839,7 +2628,7 @@ "description": "List of services to run with the suite.", "type": "array", "items": { - "$ref": "#/allOf/9/then/definitions/service" + "$ref": "#/allOf/8/then/definitions/service" } } }, @@ -2988,22 +2777,22 @@ "const": "imagerunner" }, "sauce": { - "$ref": "#/allOf/9/then/definitions/sauce" + "$ref": "#/allOf/8/then/definitions/sauce" }, "defaults": { "description": "Settings that are applied onto every suite as a default value.", - "$ref": "#/allOf/9/then/definitions/suite" + "$ref": "#/allOf/8/then/definitions/suite" }, "suites": { "description": "List of suites", "type": "array", "minItems": 1, "items": { - "$ref": "#/allOf/9/then/definitions/suite" + "$ref": "#/allOf/8/then/definitions/suite" } }, "reporters": { - "$ref": "#/allOf/9/then/definitions/reporters" + "$ref": "#/allOf/8/then/definitions/reporters" } }, "required": [ diff --git a/api/v1alpha/framework/cypress.schema.json b/api/v1alpha/framework/cypress.schema.json deleted file mode 100644 index 40cc8f406..000000000 --- a/api/v1alpha/framework/cypress.schema.json +++ /dev/null @@ -1,199 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "saucectl cypress runner configuration", - "description": "Configuration file for cypress using saucectl", - "type": "object", - "allOf": [ - { - "$ref": "../subschema/artifacts.schema.json" - }, - { - "$ref": "../subschema/npm.schema.json" - }, - { - "$ref": "../subschema/sauce.schema.json" - }, - { - "$ref": "../subschema/reporters.schema.json" - } - ], - "properties": { - "apiVersion": { - "const": "v1alpha" - }, - "kind": { - "const": "cypress" - }, - "showConsoleLog": { - "$ref": "../subschema/common.schema.json#/definitions/showConsoleLog" - }, - "defaults": { - "description": "Settings that are applied onto every suite by default, if no value is set on a suite explicitly.", - "type": "object", - "properties": { - "timeout": { - "$ref": "../subschema/common.schema.json#/definitions/timeout" - } - }, - "additionalProperties": false - }, - "rootDir": { - "$ref": "../subschema/common.schema.json#/definitions/rootDir" - }, - "cypress": { - "description": "Contains details specific to the Cypress project.", - "type": "object", - "properties": { - "configFile": { - "description": "The designated cypress configuration file. SauceCTL determines related files based on the location of the config file. By default saucectl defers to the test file location defined in cypress.json.", - "type": "string", - "examples": [ - "cypress.json" - ] - }, - "key": { - "description": "The secret key that grants permission to record your tests in the Cypress dashboard.", - "type": "string" - }, - "record": { - "description": "Whether to record your test results in the Cypress dashboard.", - "type": "boolean" - }, - "version": { - "$ref": "../subschema/common.schema.json#/definitions/version", - "enum": [ - "package.json", - "9.7.0" - ] - }, - "reporters": { - "description": "Set of reporter to use.", - "type": "array", - "minimum": 0, - "items": { - "type": "object", - "properties": { - "name": { - "description": "Name of the reporter. You may have to install the associated npm dependency through npm.packages field.", - "type": "string", - "examples": [ - "dot", - "nyan" - ] - }, - "options": { - "description": "Options to pass to the reporter." - } - }, - "additionalProperties": false - } - } - }, - "required": [ - "configFile", - "version" - ], - "additionalProperties": false - }, - "env": { - "$ref": "../subschema/common.schema.json#/definitions/env" - }, - "suites": { - "description": "The set of properties providing details about the test suites to run.", - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "name": { - "description": "The name of the test suite, which will be reflected in the test results in Sauce Labs.", - "type": "string" - }, - "browser": { - "$ref": "../subschema/common.schema.json#/definitions/browser", - "enum": [ - "chrome", - "firefox", - "microsoftedge" - ] - }, - "browserVersion": { - "$ref": "../subschema/common.schema.json#/definitions/browserVersion" - }, - "platformName": { - "$ref": "../subschema/common.schema.json#/definitions/platformName", - "enum": [ - "macOS 11.00", - "macOS 12", - "macOS 13", - "Windows 10", - "Windows 11" - ] - }, - "screenResolution": { - "$ref": "../subschema/common.schema.json#/definitions/screenResolution" - }, - "config": { - "description": "Provides details related to the Cypress test configuration.", - "type": "object", - "properties": { - "testFiles": { - "description": "One or more paths to the Cypress test files to run for this suite, if not otherwise specified explicitly in cypress.json.", - "type": "array" - }, - "excludedTestFiles": { - "$ref": "../subschema/common.schema.json#/definitions/excludedTestFiles" - }, - "env": { - "$ref": "../subschema/common.schema.json#/definitions/env" - } - }, - "required": [ - "testFiles" - ] - }, - "shard": { - "description": "When sharding is configured, saucectl automatically splits the tests (e.g. by spec or concurrency) so that they can easily run in parallel.", - "enum": [ - "", - "concurrency", - "spec" - ] - }, - "headless": { - "description": "Controls whether or not tests are run in headless mode (default: false)", - "type": "boolean" - }, - "timeout": { - "$ref": "../subschema/common.schema.json#/definitions/timeout" - }, - "preExec": { - "$ref": "../subschema/common.schema.json#/definitions/preExec" - }, - "timeZone": { - "$ref": "../subschema/common.schema.json#/definitions/timeZone" - }, - "passThreshold": { - "$ref": "../subschema/common.schema.json#/definitions/passThreshold" - }, - "smartRetry": { - "$ref": "../subschema/common.schema.json#/definitions/smartRetry" - } - }, - "required": [ - "name", - "browser", - "config" - ], - "additionalProperties": false - } - } - }, - "required": [ - "apiVersion", - "kind", - "cypress", - "suites" - ], - "additionalProperties": true -} diff --git a/internal/cmd/ini/cypress.go b/internal/cmd/ini/cypress.go index 53da686bc..c4ae80d11 100644 --- a/internal/cmd/ini/cypress.go +++ b/internal/cmd/ini/cypress.go @@ -5,15 +5,12 @@ import ( _ "embed" "fmt" "os" - "strconv" - "strings" "github.com/rs/zerolog/log" cmds "github.com/saucelabs/saucectl/internal/cmd" "github.com/saucelabs/saucectl/internal/config" "github.com/saucelabs/saucectl/internal/cypress" v1 "github.com/saucelabs/saucectl/internal/cypress/v1" - "github.com/saucelabs/saucectl/internal/cypress/v1alpha" "github.com/saucelabs/saucectl/internal/segment" "github.com/saucelabs/saucectl/internal/usage" "github.com/spf13/cobra" @@ -59,47 +56,6 @@ func CypressCmd() *cobra.Command { } func configureCypress(cfg *initConfig) interface{} { - versions := strings.Split(cfg.frameworkVersion, ".") - version, err := strconv.Atoi(versions[0]) - if err != nil { - log.Err(err).Msg("failed to parse framework version") - } - if version < 10 { - return v1alpha.Project{ - TypeDef: config.TypeDef{ - APIVersion: v1alpha.APIVersion, - Kind: cypress.Kind, - }, - Sauce: config.SauceConfig{ - Region: cfg.region, - Sauceignore: ".sauceignore", - Concurrency: cfg.concurrency, - }, - RootDir: ".", - Cypress: v1alpha.Cypress{ - Version: cfg.frameworkVersion, - ConfigFile: cfg.cypressConfigFile, - }, - Suites: []v1alpha.Suite{ - { - Name: fmt.Sprintf("cypress - %s - %s", cfg.platformName, cfg.browserName), - PlatformName: cfg.platformName, - Browser: cfg.browserName, - Config: v1alpha.SuiteConfig{ - TestFiles: []string{"**/*.*"}, - }, - }, - }, - Artifacts: config.Artifacts{ - Download: config.ArtifactDownload{ - When: cfg.artifactWhen, - Directory: "./artifacts", - Match: []string{"*"}, - }, - }, - } - } - return v1.Project{ TypeDef: config.TypeDef{ APIVersion: v1.APIVersion, diff --git a/internal/cypress/config.go b/internal/cypress/config.go index df94f1906..794a661e2 100644 --- a/internal/cypress/config.go +++ b/internal/cypress/config.go @@ -1,10 +1,11 @@ package cypress import ( + "errors" + "github.com/saucelabs/saucectl/internal/config" "github.com/saucelabs/saucectl/internal/cypress/suite" v1 "github.com/saucelabs/saucectl/internal/cypress/v1" - "github.com/saucelabs/saucectl/internal/cypress/v1alpha" "github.com/saucelabs/saucectl/internal/saucereport" ) @@ -67,8 +68,8 @@ func FromFile(cfgPath string) (Project, error) { if err != nil { return nil, err } - if version == v1alpha.APIVersion { - return v1alpha.FromFile(cfgPath) + if version == "v1alpha" { + return nil, errors.New("cypress v1alpha is no longer supported") } return v1.FromFile(cfgPath) } diff --git a/internal/cypress/v1alpha/config.go b/internal/cypress/v1alpha/config.go deleted file mode 100644 index 21e98893a..000000000 --- a/internal/cypress/v1alpha/config.go +++ /dev/null @@ -1,633 +0,0 @@ -package v1alpha - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - "unicode" - - "github.com/rs/zerolog/log" - "github.com/saucelabs/saucectl/internal/concurrency" - "github.com/saucelabs/saucectl/internal/config" - "github.com/saucelabs/saucectl/internal/cypress/suite" - "github.com/saucelabs/saucectl/internal/fpath" - "github.com/saucelabs/saucectl/internal/msg" - "github.com/saucelabs/saucectl/internal/region" - "github.com/saucelabs/saucectl/internal/sauceignore" - "github.com/saucelabs/saucectl/internal/saucereport" -) - -// Config descriptors. -var ( - // Kind represents the type definition of this config. - Kind = "cypress" - - // APIVersion represents the supported config version. - APIVersion = "v1alpha" -) - -// Project represents the cypress project configuration. -type Project struct { - config.TypeDef `yaml:",inline" mapstructure:",squash"` - Defaults config.Defaults `yaml:"defaults" json:"defaults"` - DryRun bool `yaml:"-" json:"-"` - ShowConsoleLog bool `yaml:"showConsoleLog" json:"-"` - ConfigFilePath string `yaml:"-" json:"-"` - CLIFlags map[string]interface{} `yaml:"-" json:"-"` - Sauce config.SauceConfig `yaml:"sauce,omitempty" json:"sauce"` - Cypress Cypress `yaml:"cypress,omitempty" json:"cypress"` - // Suite is only used as a workaround to parse adhoc suites that are created via CLI args. - Suite Suite `yaml:"suite,omitempty" json:"-"` - Suites []Suite `yaml:"suites,omitempty" json:"suites"` - BeforeExec []string `yaml:"beforeExec,omitempty" json:"beforeExec"` - Npm config.Npm `yaml:"npm,omitempty" json:"npm"` - RootDir string `yaml:"rootDir,omitempty" json:"rootDir"` - RunnerVersion string `yaml:"runnerVersion,omitempty" json:"runnerVersion"` - Artifacts config.Artifacts `yaml:"artifacts,omitempty" json:"artifacts"` - Reporters config.Reporters `yaml:"reporters,omitempty" json:"-"` - Env map[string]string `yaml:"env,omitempty" json:"env"` - EnvFlag map[string]string `yaml:"-" json:"-"` - Notifications config.Notifications `yaml:"notifications,omitempty" json:"-"` -} - -// Suite represents the cypress test suite configuration. -type Suite struct { - Name string `yaml:"name,omitempty" json:"name"` - Browser string `yaml:"browser,omitempty" json:"browser"` - BrowserVersion string `yaml:"browserVersion,omitempty" json:"browserVersion"` - PlatformName string `yaml:"platformName,omitempty" json:"platformName"` - Config SuiteConfig `yaml:"config,omitempty" json:"config"` - ScreenResolution string `yaml:"screenResolution,omitempty" json:"screenResolution"` - Timeout time.Duration `yaml:"timeout,omitempty" json:"timeout"` - Shard string `yaml:"shard,omitempty" json:"-"` - Headless bool `yaml:"headless,omitempty" json:"headless"` - PreExec []string `yaml:"preExec,omitempty" json:"preExec"` - TimeZone string `yaml:"timeZone,omitempty" json:"timeZone"` - PassThreshold int `yaml:"passThreshold,omitempty" json:"-"` - SmartRetry config.SmartRetry `yaml:"smartRetry,omitempty" json:"-"` -} - -// SuiteConfig represents the cypress config overrides. -type SuiteConfig struct { - TestFiles []string `yaml:"testFiles,omitempty" json:"testFiles"` - ExcludedTestFiles []string `yaml:"excludedTestFiles,omitempty" json:"ignoreTestFiles,omitempty"` - Env map[string]string `yaml:"env,omitempty" json:"env"` -} - -// Reporter represents a cypress report configuration. -type Reporter struct { - Name string `yaml:"name" json:"name"` - Options map[string]interface{} `yaml:"options" json:"options"` -} - -// Cypress represents crucial cypress configuration that is required for setting up a project. -type Cypress struct { - // ConfigFile is the path to "cypress.json". - ConfigFile string `yaml:"configFile,omitempty" json:"configFile"` - - // Version represents the cypress framework version. - Version string `yaml:"version" json:"version"` - - // Record represents the cypress framework record flag. - Record bool `yaml:"record" json:"record"` - - // Key represents the cypress framework key flag. - Key string `yaml:"key" json:"key"` - - // Reporters represents the customer reporters. - Reporters []Reporter `yaml:"reporters" json:"reporters"` -} - -// FromFile creates a new cypress Project based on the filepath cfgPath. -func FromFile(cfgPath string) (*Project, error) { - var p *Project - - if err := config.Unmarshal(cfgPath, &p); err != nil { - return p, err - } - - p.ConfigFilePath = cfgPath - - return p, nil -} - -// SetDefaults applies config defaults in case the user has left them blank. -func (p *Project) SetDefaults() { - if p.Kind == "" { - p.Kind = Kind - } - - if p.APIVersion == "" { - p.APIVersion = APIVersion - } - - if p.Sauce.Concurrency < 1 { - p.Sauce.Concurrency = 2 - } - - // Default rootDir to . - if p.RootDir == "" { - p.RootDir = "." - msg.LogRootDirWarning() - } - - if p.Defaults.Timeout < 0 { - p.Defaults.Timeout = 0 - } - - p.Sauce.Tunnel.SetDefaults() - p.Sauce.Metadata.SetDefaultBuild() - p.Npm.SetDefaults(p.Kind, p.Cypress.Version) - - for k := range p.Suites { - s := &p.Suites[k] - if s.PlatformName == "" { - s.PlatformName = "Windows 10" - log.Info().Msgf(msg.InfoUsingDefaultPlatform, s.PlatformName, s.Name) - } - - if s.Timeout <= 0 { - s.Timeout = p.Defaults.Timeout - } - - if s.Config.Env == nil { - s.Config.Env = map[string]string{} - } - - // Apply global env vars onto suite. - // Precedence: --env flag > root-level env vars > suite-level env vars. - for _, env := range []map[string]string{p.Env, p.EnvFlag} { - for k, v := range env { - s.Config.Env[k] = v - } - } - - if s.PassThreshold < 1 { - s.PassThreshold = 1 - } - - // Update cypress related env vars. - for envK := range s.Config.Env { - // Add an entry without CYPRESS_ prefix as we directly pass it in Cypress. - if strings.HasPrefix(envK, "CYPRESS_") { - newKey := strings.TrimPrefix(envK, "CYPRESS_") - s.Config.Env[newKey] = s.Config.Env[envK] - } - } - } -} - -func checkAvailability(path string, mustBeDirectory bool) error { - st, err := os.Stat(path) - if err != nil { - return err - } - if mustBeDirectory && !st.IsDir() { - return fmt.Errorf("%s: not a folder", path) - } - return nil -} - -// loadCypressConfiguration reads the cypress.json file and performs basic validation. -func loadCypressConfiguration(rootDir string, cypressCfgFile, sauceIgnoreFile string) (Config, error) { - isIgnored, err := isCypressCfgIgnored(sauceIgnoreFile, cypressCfgFile) - if err != nil { - return Config{}, err - } - if isIgnored { - return Config{}, fmt.Errorf("your .sauceignore configuration seems to include statements that match crucial cypress configuration files (e.g. cypress.json). In order to run your test successfully, please adjust your .sauceignore configuration") - } - - cypressCfgPath := filepath.Join(rootDir, cypressCfgFile) - cfg, err := configFromFile(cypressCfgPath) - if err != nil { - return Config{}, err - } - - if cfg.IntegrationFolder == "" { - cfg.IntegrationFolder = "cypress/integration" - } - - configDir := filepath.Dir(cypressCfgPath) - if err = checkAvailability(filepath.Join(configDir, cfg.IntegrationFolder), true); err != nil { - return Config{}, err - } - - // FixturesFolder sets the path to folder containing fixture files (Pass false to disable) - // ref: https://docs.cypress.io/guides/references/configuration#Folders-Files - if f, ok := cfg.FixturesFolder.(string); ok && f != "" { - if err = checkAvailability(filepath.Join(configDir, f), true); err != nil { - return Config{}, err - } - } - - if cfg.SupportFile != "" { - if err = checkAvailability(filepath.Join(configDir, cfg.SupportFile), false); err != nil { - return Config{}, err - } - } - - if cfg.PluginsFile != "" { - if err = checkAvailability(filepath.Join(configDir, cfg.PluginsFile), false); err != nil { - return Config{}, err - } - } - - return cfg, nil -} - -func isCypressCfgIgnored(sauceIgnoreFile, cypressCfgFile string) (bool, error) { - if _, err := os.Stat(sauceIgnoreFile); err != nil { - return false, nil - } - matcher, err := sauceignore.NewMatcherFromFile(sauceIgnoreFile) - if err != nil { - return false, err - } - - return matcher.Match([]string{cypressCfgFile}, false), nil -} - -// Validate validates basic configuration of the project and returns an error if any of the settings contain illegal -// values. This is not an exhaustive operation and further validation should be performed both in the client and/or -// server side depending on the workflow that is executed. -func (p *Project) Validate() error { - p.Cypress.Version = config.StandardizeVersionFormat(p.Cypress.Version) - - if p.Cypress.Version == "" { - return errors.New(msg.MissingCypressVersion) - } - - // Check rootDir exists. - if p.RootDir != "" { - if _, err := os.Stat(p.RootDir); err != nil { - return fmt.Errorf(msg.UnableToLocateRootDir, p.RootDir) - } - } - - regio := region.FromString(p.Sauce.Region) - if regio == region.None { - return errors.New(msg.MissingRegion) - } - - if ok := config.ValidateVisibility(p.Sauce.Visibility); !ok { - return fmt.Errorf(msg.InvalidVisibility, p.Sauce.Visibility, strings.Join(config.ValidVisibilityValues, ",")) - } - - err := config.ValidateRegistries(p.Npm.Registries) - if err != nil { - return err - } - - if p.Sauce.LaunchOrder != "" && p.Sauce.LaunchOrder != config.LaunchOrderFailRate { - return fmt.Errorf(msg.InvalidLaunchingOption, p.Sauce.LaunchOrder, string(config.LaunchOrderFailRate)) - } - - // Validate suites. - if len(p.Suites) == 0 { - return errors.New(msg.EmptySuite) - } - suiteNames := make(map[string]bool) - for idx, s := range p.Suites { - if _, seen := suiteNames[s.Name]; seen { - return fmt.Errorf(msg.DuplicateSuiteName, s.Name) - } - suiteNames[s.Name] = true - - if len(s.Name) == 0 { - return fmt.Errorf(msg.MissingSuiteName, idx) - } - - for _, c := range s.Name { - if unicode.IsSymbol(c) { - return fmt.Errorf(msg.IllegalSymbol, c, s.Name) - } - } - - if s.Browser == "" { - return fmt.Errorf(msg.MissingBrowserInSuite, s.Name) - } - - if s.PlatformName == "" { - return fmt.Errorf(msg.MissingPlatformName) - } - - if len(s.Config.TestFiles) == 0 { - return fmt.Errorf(msg.MissingTestFiles, s.Name) - } - if p.Sauce.Retries < s.PassThreshold-1 { - return fmt.Errorf(msg.InvalidPassThreshold) - } - } - if p.Sauce.Retries < 0 { - log.Warn().Int("retries", p.Sauce.Retries).Msg(msg.InvalidReries) - } - - cfg, err := loadCypressConfiguration(p.RootDir, p.Cypress.ConfigFile, p.Sauce.Sauceignore) - if err != nil { - return err - } - - if p.Suites, err = shardSuites(cfg, p.Suites, p.Sauce.Concurrency, p.Sauce.Sauceignore); err != nil { - return err - } - if len(p.Suites) == 0 { - return errors.New(msg.EmptySuite) - } - return nil -} - -func shardSuites(cfg Config, suites []Suite, ccy int, sauceignoreFile string) ([]Suite, error) { - var shardedSuites []Suite - for _, s := range suites { - // Use the original suite if there is nothing to shard. - if s.Shard != "spec" && s.Shard != "concurrency" { - shardedSuites = append(shardedSuites, s) - continue - } - files, err := fpath.FindFiles(cfg.AbsIntegrationFolder(), s.Config.TestFiles, fpath.FindByShellPattern) - if err != nil { - return shardedSuites, err - } - if len(files) == 0 { - msg.SuiteSplitNoMatch(s.Name, cfg.AbsIntegrationFolder(), s.Config.TestFiles) - return []Suite{}, fmt.Errorf("suite '%s' patterns have no matching files", s.Name) - } - excludedFiles, err := fpath.FindFiles(cfg.AbsIntegrationFolder(), s.Config.ExcludedTestFiles, fpath.FindByShellPattern) - if err != nil { - return shardedSuites, err - } - - files = sauceignore.ExcludeSauceIgnorePatterns(files, sauceignoreFile) - testFiles := fpath.ExcludeFiles(files, excludedFiles) - - if s.Shard == "spec" { - for _, f := range testFiles { - replica := s - replica.Name = fmt.Sprintf("%s - %s", s.Name, f) - replica.Config.TestFiles = []string{f} - shardedSuites = append(shardedSuites, replica) - } - } - if s.Shard == "concurrency" { - fileGroups := concurrency.BinPack(testFiles, ccy) - for i, group := range fileGroups { - replica := s - replica.Name = fmt.Sprintf("%s - %d/%d", s.Name, i+1, len(fileGroups)) - replica.Config.TestFiles = group - shardedSuites = append(shardedSuites, replica) - } - } - } - - return shardedSuites, nil -} - -// FilterSuites filters out suites in the project that don't match the given suite name. -func (p *Project) FilterSuites(suiteName string) error { - for _, s := range p.Suites { - if s.Name == suiteName { - p.Suites = []Suite{s} - return nil - } - } - return fmt.Errorf(msg.SuiteNameNotFound, suiteName) -} - -// IsSharded returns is it's sharded -func (p *Project) IsSharded() bool { - for _, s := range p.Suites { - if s.Shard != "" { - return true - } - } - return false -} - -// CleanPackages removes cypress from npm packages -func (p *Project) CleanPackages() { - // Don't allow framework installation, it is provided by the runner - version, hasFramework := p.Npm.Packages["cypress"] - if hasFramework { - log.Warn().Msg(msg.IgnoredNpmPackagesMsg("cypress", p.Cypress.Version, []string{fmt.Sprintf("cypress@%s", version)})) - p.Npm.Packages = config.CleanNpmPackages(p.Npm.Packages, []string{"cypress"}) - } -} - -// GetSuiteCount returns the amount of suites -func (p *Project) GetSuiteCount() int { - if p == nil { - return 0 - } - return len(p.Suites) -} - -// GetVersion returns cypress version -func (p *Project) GetVersion() string { - return p.Cypress.Version -} - -// GetRunnerVersion returns RunnerVersion -func (p *Project) GetRunnerVersion() string { - return p.RunnerVersion -} - -// SetVersion sets cypress version -func (p *Project) SetVersion(version string) { - p.Cypress.Version = version -} - -// SetRunnerVersion sets runner version -func (p *Project) SetRunnerVersion(version string) { - p.RunnerVersion = version -} - -// GetSauceCfg returns sauce related config -func (p *Project) GetSauceCfg() config.SauceConfig { - return p.Sauce -} - -// IsDryRun returns DryRun -func (p *Project) IsDryRun() bool { - return p.DryRun -} - -// GetRootDir returns RootDir -func (p *Project) GetRootDir() string { - return p.RootDir -} - -// GetSuiteNames returns combined suite names -func (p *Project) GetSuiteNames() string { - var names []string - for _, s := range p.Suites { - names = append(names, s.Name) - } - return strings.Join(names, ", ") -} - -// GetCfgPath returns ConfigFilePath -func (p *Project) GetCfgPath() string { - return p.ConfigFilePath -} - -// GetCLIFlags returns CLIFlags -func (p *Project) GetCLIFlags() map[string]interface{} { - return p.CLIFlags -} - -// GetArtifactsCfg returns config.Artifacts -func (p *Project) GetArtifactsCfg() config.Artifacts { - return p.Artifacts -} - -// IsShowConsoleLog returns ShowConsoleLog -func (p *Project) IsShowConsoleLog() bool { - return p.ShowConsoleLog -} - -// GetBeforeExec returns BeforeExec -func (p *Project) GetBeforeExec() []string { - return p.BeforeExec -} - -// GetReporter returns config.Reporters -func (p *Project) GetReporters() config.Reporters { - return p.Reporters -} - -// GetNotifications returns config.Notifications -func (p *Project) GetNotifications() config.Notifications { - return p.Notifications -} - -// GetNpm returns config.Npm -func (p *Project) GetNpm() config.Npm { - return p.Npm -} - -// SetCLIFlags sets cli flags -func (p *Project) SetCLIFlags(flags map[string]interface{}) { - p.CLIFlags = flags -} - -// GetSuites returns suites -func (p *Project) GetSuites() []suite.Suite { - suites := []suite.Suite{} - for _, s := range p.Suites { - suites = append(suites, suite.Suite{ - Name: s.Name, - Browser: s.Browser, - BrowserVersion: s.BrowserVersion, - PlatformName: s.PlatformName, - ScreenResolution: s.ScreenResolution, - Timeout: s.Timeout, - Shard: s.Shard, - Headless: s.Headless, - PreExec: s.PreExec, - TimeZone: s.TimeZone, - Env: s.Config.Env, - PassThreshold: s.PassThreshold, - }) - } - return suites -} - -// GetKind returns Kind -func (p *Project) GetKind() string { - return p.Kind -} - -// GetSuite returns suite -func (p *Project) GetSuite() suite.Suite { - s := p.Suite - return suite.Suite{ - Name: s.Name, - Browser: s.Browser, - BrowserVersion: s.BrowserVersion, - PlatformName: s.PlatformName, - ScreenResolution: s.ScreenResolution, - Timeout: s.Timeout, - Shard: s.Shard, - Headless: s.Headless, - PreExec: s.PreExec, - TimeZone: s.TimeZone, - Env: s.Config.Env, - PassThreshold: s.PassThreshold, - } -} - -// ApplyFlags applys cli flags on cypress project -func (p *Project) ApplyFlags(selectedSuite string) error { - if selectedSuite != "" { - if err := p.FilterSuites(selectedSuite); err != nil { - return err - } - } - - // Create an adhoc suite if "--name" is provided - if p.Suite.Name != "" { - p.Suites = []Suite{p.Suite} - } - - return nil -} - -// AppendTags adds tags -func (p *Project) AppendTags(tags []string) { - p.Sauce.Metadata.Tags = append(p.Sauce.Metadata.Tags, tags...) -} - -// GetAPIVersion returns APIVersion -func (p *Project) GetAPIVersion() string { - return p.APIVersion -} - -// GetSmartRetry returns smartRetry config -func (p *Project) GetSmartRetry(suiteName string) config.SmartRetry { - for _, s := range p.Suites { - if s.Name == suiteName { - return s.SmartRetry - } - } - return config.SmartRetry{} -} - -// FilterFailedTests takes the failed tests in the report and sets them as a test filter in the suite. -// The test filter remains unchanged if the report does not contain any failed tests. -func (p *Project) FilterFailedTests(suiteName string, report saucereport.SauceReport) error { - failedTests := saucereport.GetFailedTests(report) - if len(failedTests) == 0 { - return nil - } - - var found bool - for i, s := range p.Suites { - if s.Name != suiteName { - continue - } - found = true - if p.Suites[i].Config.Env == nil { - p.Suites[i].Config.Env = map[string]string{} - } - p.Suites[i].Config.Env["grep"] = strings.Join(failedTests, ";") - - } - if !found { - return fmt.Errorf("suite(%s) not found", suiteName) - } - return nil -} - -// IsSmartRetried checks if the suites contain a smartRetried suite -func (p *Project) IsSmartRetried() bool { - for _, s := range p.Suites { - if s.SmartRetry.IsRetryFailedOnly() { - return true - } - } - return false -} diff --git a/internal/cypress/v1alpha/config_test.go b/internal/cypress/v1alpha/config_test.go deleted file mode 100644 index d18e8aa40..000000000 --- a/internal/cypress/v1alpha/config_test.go +++ /dev/null @@ -1,531 +0,0 @@ -package v1alpha - -import ( - "errors" - "fmt" - "os" - "reflect" - "testing" - - "github.com/saucelabs/saucectl/internal/saucereport" - "github.com/stretchr/testify/assert" - "gotest.tools/v3/fs" -) - -func TestFilterSuites(t *testing.T) { - testCase := []struct { - name string - config *Project - suiteName string - expConfig Project - expErr string - }{ - { - name: "filtered suite exists in config", - config: &Project{Suites: []Suite{ - { - Name: "suite1", - }, - { - Name: "suite2", - }, - }}, - suiteName: "suite1", - expConfig: Project{Suites: []Suite{ - { - Name: "suite1", - }, - }}, - }, - { - name: "filtered suite does not exist in config", - config: &Project{Suites: []Suite{ - { - Name: "suite1", - }, - { - Name: "suite2", - }, - }}, - suiteName: "suite3", - expConfig: Project{Suites: []Suite{ - { - Name: "suite1", - }, - { - Name: "suite2", - }, - }}, - expErr: "no suite named 'suite3' found", - }, - } - - for _, tc := range testCase { - t.Run(tc.name, func(t *testing.T) { - err := tc.config.FilterSuites(tc.suiteName) - if err != nil { - assert.Equal(t, tc.expErr, err.Error()) - } - assert.True(t, reflect.DeepEqual(*tc.config, tc.expConfig)) - }) - } -} - -func Test_loadCypressConfiguration(t *testing.T) { - tests := []struct { - creator func(t *testing.T) *fs.Dir - name string - errFileName string - wantErr string - }{ - { - name: "Valid empty config", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", "{}", fs.WithMode(0644)), - fs.WithDir("cypress", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)))) - return dir - }, - wantErr: "", - }, - { - name: "Valid config with custom integration folder", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)))) - return dir - }, - wantErr: "", - }, - { - name: "Valid config with custom integration/fixtures folders, plugins/support files", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration", "fixturesFolder": "e2e/fixtures", "pluginsFile": "e2e/plugins-custom/index.js", "supportFile": "e2e/support-custom/index.js"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)), - fs.WithDir("fixtures", fs.WithMode(0755)), - fs.WithDir("plugins-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))), - fs.WithDir("support-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))))) - return dir - }, - wantErr: "", - }, - { - name: "Invalid file", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFo}`, fs.WithMode(0644))) - return dir - }, - wantErr: "unexpected EOF", - }, - { - name: "Un-existing integrationFolder", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration-absent", "fixturesFolder": "e2e/fixtures", "pluginsFile": "e2e/plugins-custom/index.js", "supportFile": "e2e/support-custom/index.js"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)), - fs.WithDir("fixtures", fs.WithMode(0755)), - fs.WithDir("plugins-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))), - fs.WithDir("support-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))))) - return dir - }, - errFileName: "e2e/integration-absent", - wantErr: "stat %s: no such file or directory", - }, - { - name: "Un-existing fixture folder", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration", "fixturesFolder": "e2e/fixtures-absent", "pluginsFile": "e2e/plugins-custom/index.js", "supportFile": "e2e/support-custom/index.js"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)), - fs.WithDir("fixtures", fs.WithMode(0755)), - fs.WithDir("plugins-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))), - fs.WithDir("support-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))))) - return dir - }, - errFileName: "e2e/fixtures-absent", - wantErr: "stat %s: no such file or directory", - }, - { - name: "Un-existing plugins file", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration", "fixturesFolder": "e2e/fixtures", "pluginsFile": "e2e/plugins-custom/index-fake.js", "supportFile": "e2e/support-custom/index.js"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)), - fs.WithDir("fixtures", fs.WithMode(0755)), - fs.WithDir("plugins-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))), - fs.WithDir("support-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))))) - return dir - }, - errFileName: "e2e/plugins-custom/index-fake.js", - wantErr: "stat %s: no such file or directory", - }, - { - name: "Un-existing support file", - creator: func(t *testing.T) *fs.Dir { - dir := fs.NewDir(t, "test-case", fs.WithMode(0755), - fs.WithFile("cypress.json", `{"integrationFolder":"e2e/integration", "fixturesFolder": "e2e/fixtures", "pluginsFile": "e2e/plugins-custom/index.js", "supportFile": "e2e/support-custom/index-fake.js"}`, fs.WithMode(0644)), - fs.WithDir("e2e", fs.WithMode(0755), - fs.WithDir("integration", fs.WithMode(0755)), - fs.WithDir("fixtures", fs.WithMode(0755)), - fs.WithDir("plugins-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))), - fs.WithDir("support-custom", fs.WithMode(0755), - fs.WithFile("index.js", "", fs.WithMode(0644))))) - return dir - }, - errFileName: "e2e/support-custom/index-fake.js", - wantErr: "stat %s: no such file or directory", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d := tt.creator(t) - defer d.Remove() - _, err := loadCypressConfiguration(d.Path(), "cypress.json", ".sauceignore") - - if tt.wantErr != "" { - expectedErr := tt.wantErr - if tt.errFileName != "" { - expectedErr = fmt.Sprintf(tt.wantErr, d.Join(tt.errFileName)) - } - assert.EqualError(t, err, expectedErr, "ValidateCypressConfiguration() error = %v, wantErr %v", err, expectedErr) - } else { - assert.Nil(t, err, "ValidateCypressConfiguration() error = %v, wanted no-error", err) - } - }) - } -} - -func Test_shardSuites(t *testing.T) { - dir := fs.NewDir(t, "cypress", - fs.WithFile(".sauceignore", "", fs.WithMode(0644)), - fs.WithMode(0755), - fs.WithDir("cypress", - fs.WithMode(0755), - fs.WithDir("integration", - fs.WithMode(0755), - fs.WithDir("a", - fs.WithMode(0755), - fs.WithFile("todo.spec.js", "dummy", fs.WithMode(0644)), - fs.WithDir("b", - fs.WithMode(0755), - fs.WithFile("todo.spec.js", "dummy", fs.WithMode(0644)), - fs.WithDir("c", - fs.WithMode(0755), - fs.WithFile("todo.spec.js", "dummy", fs.WithMode(0644)), - ), - ), - ), - fs.WithFile("file1.spec.js", "dummy", fs.WithMode(0644)), - fs.WithFile("file2.spec.js", "dummy", fs.WithMode(0644)), - ))) - - defer dir.Remove() - - oldPwd, _ := os.Getwd() - defer func() { - if err := os.Chdir(oldPwd); err != nil { - t.Errorf("failed to change directory to %s: %v", oldPwd, err) - } - }() - if err := os.Chdir(dir.Path()); err != nil { - t.Errorf("failed to change directory to %s: %v", dir.Path(), err) - } - - type args struct { - cfg Config - suites []Suite - } - tests := []struct { - name string - args args - want []Suite - wantErr error - }{ - { - name: "Single suite", - args: args{ - cfg: Config{ - Path: ".", - IntegrationFolder: "cypress/integration/", - }, - suites: []Suite{ - { - Name: "Demo #1", - Config: SuiteConfig{ - TestFiles: []string{"**/*.spec.js"}, - }, - Shard: "none", - }, - }, - }, - want: []Suite{ - { - Name: "Demo #1", - Config: SuiteConfig{ - TestFiles: []string{"**/*.spec.js"}, - }, - Shard: "none", - }, - }, - wantErr: nil, - }, - { - name: "Sharded suite", - args: args{ - cfg: Config{ - Path: ".", - IntegrationFolder: "cypress/integration/", - }, - suites: []Suite{ - { - Name: "Demo #1", - Config: SuiteConfig{ - TestFiles: []string{"**/*.spec.js"}, - }, - Shard: "spec", - }, - }, - }, - want: []Suite{ - { - Name: "Demo #1 - a/b/c/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/b/c/todo.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - a/b/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/b/todo.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - a/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/todo.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - file1.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"file1.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - file2.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"file2.spec.js"}, - }, - Shard: "spec", - }, - }, - wantErr: nil, - }, - { - name: "Sharded suite - no match", - args: args{ - cfg: Config{ - Path: ".", - IntegrationFolder: "cypress/integration/", - }, - suites: []Suite{ - { - Name: "Demo #1", - Config: SuiteConfig{ - TestFiles: []string{"**/*.fail.js"}, - }, - Shard: "spec", - }, - }, - }, - want: []Suite{}, - wantErr: errors.New("suite 'Demo #1' patterns have no matching files"), - }, - { - name: "Sharded with hard prefix", - args: args{ - cfg: Config{ - Path: ".", - IntegrationFolder: "cypress/integration/", - }, - suites: []Suite{ - { - Name: "Demo #1", - Config: SuiteConfig{ - TestFiles: []string{"a/**/todo.spec.js"}, - }, - Shard: "spec", - }, - }, - }, - want: []Suite{ - { - Name: "Demo #1 - a/b/c/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/b/c/todo.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - a/b/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/b/todo.spec.js"}, - }, - Shard: "spec", - }, - { - Name: "Demo #1 - a/todo.spec.js", - Config: SuiteConfig{ - TestFiles: []string{"a/todo.spec.js"}, - }, - Shard: "spec", - }, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := shardSuites(tt.args.cfg, tt.args.suites, 1, dir.Join(".sauceignore")) - assert.Equal(t, tt.wantErr, err, "err for shardSuites(%v, %v)", tt.args.cfg, tt.args.suites) - assert.Equalf(t, tt.want, got, "shardSuites(%v, %v)", tt.args.cfg, tt.args.suites) - }) - } -} - -func TestCypressV1Alpha_FilterFailedTests(t *testing.T) { - testcases := []struct { - name string - suiteName string - report saucereport.SauceReport - project *Project - expResult string - expErr error - }{ - { - name: "it should set failed tests to specified suite", - suiteName: "my suite", - report: saucereport.SauceReport{ - Status: saucereport.StatusFailed, - Suites: []saucereport.Suite{ - { - Name: "my suite", - Status: saucereport.StatusFailed, - Tests: []saucereport.Test{ - { - Status: saucereport.StatusFailed, - Name: "failed test1", - }, - { - Status: saucereport.StatusFailed, - Name: "failed test2", - }, - }, - }, - }, - }, - project: &Project{ - Suites: []Suite{ - { - Name: "my suite", - }, - }, - }, - expResult: "failed test1;failed test2", - expErr: nil, - }, - { - name: "it should keep the original settings when suiteName doesn't exist in the project", - suiteName: "my suite2", - report: saucereport.SauceReport{ - Status: saucereport.StatusFailed, - Suites: []saucereport.Suite{ - { - Name: "my suite", - Status: saucereport.StatusFailed, - Tests: []saucereport.Test{ - { - Status: saucereport.StatusFailed, - Name: "failed test1", - }, - { - Status: saucereport.StatusFailed, - Name: "failed test2", - }, - }, - }, - }, - }, - project: &Project{ - Suites: []Suite{ - { - Name: "my suite", - }, - }, - }, - expResult: "", - expErr: errors.New("suite(my suite2) not found"), - }, - { - name: "it should keep the original settings when no failed test in SauceReport", - suiteName: "my suite", - report: saucereport.SauceReport{ - Status: saucereport.StatusPassed, - Suites: []saucereport.Suite{ - { - Name: "my suite", - Status: saucereport.StatusPassed, - Tests: []saucereport.Test{ - { - Status: saucereport.StatusPassed, - Name: "passed test1", - }, - { - Status: saucereport.StatusSkipped, - Name: "skipped test2", - }, - }, - }, - }, - }, - project: &Project{ - Suites: []Suite{ - { - Name: "my suite", - }, - }, - }, - expResult: "", - expErr: nil, - }, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - err := tc.project.FilterFailedTests(tc.suiteName, tc.report) - assert.Equal(t, tc.expErr, err) - assert.Equal(t, tc.expResult, tc.project.Suites[0].Config.Env["grep"]) - }) - } -} diff --git a/internal/cypress/v1alpha/native.go b/internal/cypress/v1alpha/native.go deleted file mode 100644 index 837eaeae4..000000000 --- a/internal/cypress/v1alpha/native.go +++ /dev/null @@ -1,39 +0,0 @@ -package v1alpha - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/saucelabs/saucectl/internal/msg" -) - -// Config represents the cypress.json native configuration file. -type Config struct { - // Path is the location of the config file itself. - Path string `yaml:"-" json:"-"` - - FixturesFolder interface{} `json:"fixturesFolder,omitempty"` - IntegrationFolder string `json:"integrationFolder,omitempty"` - PluginsFile string `json:"pluginsFile,omitempty"` - SupportFile string `json:"supportFile,omitempty"` -} - -// AbsIntegrationFolder returns the absolute path to Config.IntegrationFolder. -func (c Config) AbsIntegrationFolder() string { - return filepath.Join(filepath.Join(filepath.Dir(c.Path), c.IntegrationFolder)) -} - -// configFromFile loads cypress configuration into Config structure. -func configFromFile(fileName string) (Config, error) { - var c Config - - fd, err := os.Open(fileName) - if err != nil { - return c, fmt.Errorf(msg.UnableToLocateCypressCfg, fileName) - } - err = json.NewDecoder(fd).Decode(&c) - c.Path = fileName - return c, err -} diff --git a/internal/cypress/v1alpha/native_test.go b/internal/cypress/v1alpha/native_test.go deleted file mode 100644 index 7cb20252a..000000000 --- a/internal/cypress/v1alpha/native_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package v1alpha - -import ( - "path/filepath" - "reflect" - "testing" - - "gotest.tools/v3/fs" -) - -func TestConfigFromFile(t *testing.T) { - tests := []struct { - name string - fileContent string - want Config - wantErr bool - }{ - { - name: "Valid File - Empty", - fileContent: `{}`, - want: Config{}, - wantErr: false, - }, - { - name: "Valid File - Integration folder", - fileContent: `{"integrationFolder":"./e2e/integration"}`, - want: Config{IntegrationFolder: "./e2e/integration"}, - wantErr: false, - }, - { - name: "Invalid File", - fileContent: `{"integrationFolder":"./e2e/integration}`, - want: Config{}, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dir := fs.NewDir(t, "cypress-config", fs.WithMode(0755), - fs.WithFile("cypress.json", tt.fileContent, fs.WithMode(0644))) - defer dir.Remove() - - tt.want.Path = filepath.Join(dir.Path(), "cypress.json") - - got, err := configFromFile(dir.Join("cypress.json")) - if (err != nil) != tt.wantErr { - t.Errorf("configFromFile() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("configFromFile() got = %v, want %v", got, tt.want) - } - }) - } -}