webdriverio / visual-testing

Image comparison / visual regression testing for WebdriverIO
https://webdriver.io/docs/visual-testing
MIT License
109 stars 38 forks source link

"TypeError: browser.saveScreen is not a function" if try to run tests on Saucelabs #124

Closed idem7x closed 8 months ago

idem7x commented 1 year ago

Environment (please complete the following information):

Config of WebdriverIO + wdio-image-comparison-service

[
        'image-comparison',
        // The options
        {
            // Some options, see the docs for more
            baselineFolder: join(
                process.cwd(),
                './tests/sauceLabsBaseline/'
            ),
            formatImageName: '{tag}-{logName}-{width}x{height}',
            screenshotPath: join(process.cwd(), '.tmp/'),
            savePerInstance: true,
            autoSaveBaseline: true,
            blockOutStatusBar: true,
            blockOutToolBar: true
        },
    ]

Describe the bug

import LoginPage from '../pageobjects/login.page';
import SecurePage from '../pageobjects/secure.page';

describe('My Login application', () => {
    it('should login with valid credentials', async () => {
        await LoginPage.open();

        await LoginPage.login('tomsmith', 'SuperSecretPassword!');
        await expect(SecurePage.flashAlert).toBeExisting();
        await expect(SecurePage.flashAlert).toHaveTextContaining(
            'You logged into a secure area!');

        await browser.saveScreen('examplePaged', {
            /* some options */
        })
    });
});

If I run this test locally - it passes. If I run it on Saucelabs, I get **TypeError: browser.saveScreen is not a function**.

To Reproduce To reproduce bug clone https://github.com/idem7x/wdiosauce.git and follow instructions in README.

Expected behaviour No error appears, screenshot is added.

Log

[0-0] 2023-06-16T12:33:51.705Z INFO webdriver: [POST] https://ondemand.eu-central-1.saucelabs.com/wd/hub/session/aba2c70a593f499a96c2adebe04b5eb6/execute/sync
[0-0] 2023-06-16T12:33:51.705Z INFO webdriver: DATA {
[0-0]   script: 'sauce:context=TypeError: browser.saveScreen is not a function',
[0-0]   args: []
[0-0] }
wswebcreation commented 9 months ago

This is related to mixed configs. Can you please share all your configs and the order which you import them?

wbigno commented 8 months ago

We recently upgraded to v8 on webdriverio and we too are now seeing this issue on sauce. If we run the same code on a local browser it runs fine. When we try to run it to sauce, either from our local or from Jenkins we get the "is not a function" error on every test.

package.json modules:

"devDependencies": {
        "@wdio/allure-reporter": "8.26.3",
        "@wdio/cli": "8.26.3",
        "@wdio/devtools-service": "8.26.3",
        "@wdio/local-runner": "8.26.3",
        "@wdio/mocha-framework": "8.26.3",
        "@wdio/sauce-service": "8.26.3",
        "wdio-timeline-reporter": "5.1.4",
        "allure-commandline": "2.25.0",
        "chai": "4.3.10",
        "chai-html": "2.1.0",
        "chai-match-pattern": "1.3.0",
        "chromedriver": "119.0.0",
        "eslint": "8.39.0",
        "eslint-config-google": "0.14.0",
        "eslint-plugin-babel": "5.3.1",
        "eslint-plugin-mocha": "6.3.0",
        "global-agent": "3.0.0",
        "got": "14.0.0",
        "hpagent": "1.2.0",
        "http": "0.0.1-security",
        "https": "1.0.0",
        "jsdom": "16.7.0",
        "wdio-image-comparison-service": "5.0.3",
        "webdriverio": "8.26.3",
        "write-excel-file": "1.4.27",
        "xlsx": "0.18.5",
        "xml2js": "0.4.23"
    }

wide config file

capabilities: [
        {
            // maxInstances can get overwritten per capability. So if you have an in-house Selenium
            // grid with only 5 firefox instances available you can make sure that not more than
            // 5 instances get started at a time.
            //maxInstances: 10,
            //
            browserName: 'chrome',
            'goog:chromeOptions': {
                args: ['--disable-infobars', '--window-size=1920,1440'],
                prefs: { 'download.default_directory': 'C:\\Users\\Administrator\\Downloads' }
            }
            // If outputDir is provided WebdriverIO can capture driver session logs
            // it is possible to configure which logTypes to include/exclude.
            // excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
            // excludeDriverLogs: ['bugreport', 'server'],
        }
    ],
    //
    // ===================
    // Test Configurations
    // ===================
    // Define all options that are relevant for the WebdriverIO instance here
    //
    // Level of logging verbosity: trace | debug | info | warn | error | silent
    logLevel: 'debug',
    outputDir: '.wdio/',
    deprecationWarnings: true,
    //
    // Set specific log levels per logger
    // loggers:
    // - webdriver, webdriverio
    // - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
    // - @wdio/mocha-framework, @wdio/jasmine-framework
    // - @wdio/local-runner, @wdio/lambda-runner
    // - @wdio/sumologic-reporter
    // - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
    // Level of logging verbosity: trace | debug | info | warn | error | silent
    logLevels: {
        webdriver: 'debug',
        '@wdio/devtools-service': 'debug',
        outputDir: '.wdio/devtools/',
        deprecationWarnings: true
    },
    //
    // If you only want to run your tests until a specific amount of tests have failed use
    // bail (default is 0 - don't bail, run all tests).
    bail: 0,
    //
    // Set a base URL in order to shorten url command calls. If your `url` parameter starts
    // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
    // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
    // gets prepended directly.
    baseUrl: baseUrl,
    //
    // Default timeout for all waitFor* commands.
    waitforTimeout: elementTimeout,
    //
    // Default timeout in milliseconds for request
    // if browser driver or grid doesn't send response
    connectionRetryTimeout: 180000,
    //
    // Default request retries count
    connectionRetryCount: 5,
    //
    // Test runner services
    // Services take over a specific job you don't want to take care of. They enhance
    // your test setup with almost no effort. Unlike plugins, they don't add new
    // commands. Instead, they hook themselves up into the test process.
    services: [
        [
            'image-comparison',
            {
                baselineFolder: join(process.cwd(), 'visual-regression/'),
                formatImageName: process.env.DOM_BUILD_ENV + '--{browserName}--{browserVersion}-{tag}',
                screenshotPath: join(process.cwd(), 'tmp/'),
                savePerInstance: true,
                autoSaveBaseline: true,
                blockOutStatusBar: true,
                blockOutToolBar: true,
                clearRuntimeFolder: true,
                autoElementScroll: false
            }
        ],
        ['devtools'],
        [TimelineService]
    ],
    // Framework you want to run your specs with.
    // The following are supported: Mocha, Jasmine, and Cucumber
    // see also: https://webdriver.io/docs/frameworks.html
    //
    // Make sure you have the wdio adapter package for the specific framework installed
    // before running any tests.
    framework: 'mocha',
    //
    // The number of times to retry the entire specfile when it fails as a whole
    // specFileRetries: 1,
    //
    // Whether or not retried specfiles should be retried immediately or deferred to the end of the queue
    // specFileRetriesDeferred: false,
    //
    // Test reporter for stdout.
    // The only one supported by default is 'dot'
    // see also: https://webdriver.io/docs/dot-reporter.html
    reporters: [
        [
            'timeline',
            {
                outputDir: 'report/html-report',
                fileName: 'timeline-report.html',
                embedImages: false,
                images: {
                    quality: 80,
                    resize: true,
                    reductionRatio: 2
                },

                screenshotStrategy: 'none'
            }
        ]
    ],
    //
    // Options to be passed to Mocha.
    // See the full list at http://mochajs.org/
    mochaOpts: {
        ui: 'bdd',
        timeout: timeout,
        require: []
    },

sauce config file:

import { bootstrap } from 'global-agent';
import { join } from 'path';
import { config as baseConfig } from './wdio.conf.js';
import { config as sauce } from './conf.js';
import { TimelineService } from 'wdio-timeline-reporter/timeline-service.js';

const localCaInfo = process.env.NODE_EXTRA_CA_CERTS;

bootstrap();

const defaultMacBrowserSauceOptions = {
    build: `WebdriverIOPipeline-V7 build-${process.env.DOM_BUILD_ENV}-${new Date().getTime()}`,
    screenResolution: '2360x1770',
    seleniumVersion: '3.141.59',
    idleTimeout: '1000',
    maxDuration: '4500',
    commandTimeout: '600'
};
// removed extendeding logging to improve performance until we implement the sauce intercept
const defaultWindowsBrowserSauceOptions = {
    build: `WebdriverIOPipeline-V7 build-${process.env.DOM_BUILD_ENV}-${new Date().getTime()}`,
    screenResolution: '2560x1600',
    seleniumVersion: '3.141.59',
    idleTimeout: '1000',
    maxDuration: '4500',
    commandTimeout: '600'
};

const chromeOptions = {
    'goog:chromeOptions': {
        w3c: true,
        args: ['--user-agent=******'],
        prefs: {
            'download.default_directory': 'C:\\Users\\Administrator\\Downloads',
            'download.prompt_for_download': false,
            'safebrowsing.enabled': true
        }
    }
};

// override the wdio with sauce conf
const sauceConfig = Object.assign(baseConfig, {
    protocol: 'http',
    user: sauce.user,
    key: sauce.key,
    maxInstances: 90,
    region: 'us',
    hostname: 'ondemand.us-west-1.saucelabs.com',

    // Test runner services
    // Services take over a specific job you don't want to take care of. They enhance
    // your test setup with almost no effort. Unlike plugins, they don't add new
    // commands. Instead, they hook themselves up into the test process.
    services: [
        [
            'sauce',
            {
                sauceConnect: true,
                sauceConnectOpts: {
                    proxyTunnel: true,
                    proxy: 'zproxy.*******.com:9480',
                    pac: 'http://pac.zscaler.net/*******.com/proxy.pac',
                    tunnelCainfo: localCaInfo,
                    logger: async function (message) {
                        console.log(message);
                    }
                },
                setJobNameInBeforeSuite: true
            }
        ],
        [
            ('image-comparison',
            {
                baselineFolder: join(process.cwd(), `visual-regression/${process.env.DOM_BUILD_ENV}/`),
                formatImageName: '{browserName}--{browserVersion}-{tag}',
                screenshotPath: join(process.cwd(), `tmp/${process.env.DOM_BUILD_ENV}/`),
                misMatchTolerance: 0.05,
                savePerInstance: true,
                autoSaveBaseline: true,
                blockOutStatusBar: true,
                blockOutToolBar: true,
                autoElementScroll: false,
                fullPageScrollTimeout: '5000',
                disableCSSAnimation: true
            })
        ],
        [TimelineService]
    ],
    // If you have trouble getting all important capabilities together, check out the
    // Sauce Labs platform configurator - a great tool to configure your capabilities:
    // https://docs.saucelabs.com/reference/platforms-configurator
    //
    capabilities: [
        // maxInstances can get overwritten per capability. So if you have an in-house Selenium
        // grid with only 5 firefox instances available you can make sure that not more than
        // 5 instances get started at a time.
        {
            maxInstances: 50,
            browserName: 'googlechrome',
            platformName: 'Windows 10',
            browserVersion: '117',
            'sauce:options': {
                ...defaultWindowsBrowserSauceOptions
            },
            ...chromeOptions
        }
        // { 'maxInstances': 40, 'browserName': 'googlechrome', 'platformName': 'Windows 10', 'browserVersion': 'latest-1',
        //   'sauce:options': {
        //     ...defaultWindowsBrowserSauceOptions,
        //   },
        //   ...chromeOptions,
        // },
        // { 'maxInstances': 20, 'browserName': 'chrome', 'platformName': 'macOS 10.15', 'browserVersion': 'latest',
        //   'sauce:options': {
        //     ...defaultMacBrowserSauceOptions,
        //   },
        // },
        // { 'maxInstances': 20, 'browserName': 'chrome', 'platformName': 'macOS 10.15', 'browserVersion': 'latest-1',
        //   'sauce:options': {
        //     ...defaultMacBrowserSauceOptions,
        //   },
        // },
    ]

    // afterHook: function (test, context, { error, result, duration, passed, retries }) {
    // },
    /**
     * Function to be executed after a test (in Mocha/Jasmine).
     */

    // afterTest: async function (test, context, { error, result, duration, passed, retries }) {
    //     if (error) {
    //         await browser.takeScreenshot();
    //     }
    // }
});

export const config = sauceConfig;

We are running node 18 and npm 10.

wswebcreation commented 8 months ago

@wbigno

Why do you have this in your sauce config

        [
            ('image-comparison',
            {
                baselineFolder: join(process.cwd(), `visual-regression/${process.env.DOM_BUILD_ENV}/`),
                formatImageName: '{browserName}--{browserVersion}-{tag}',
                screenshotPath: join(process.cwd(), `tmp/${process.env.DOM_BUILD_ENV}/`),
                misMatchTolerance: 0.05,
                savePerInstance: true,
                autoSaveBaseline: true,
                blockOutStatusBar: true,
                blockOutToolBar: true,
                autoElementScroll: false,
                fullPageScrollTimeout: '5000',
                disableCSSAnimation: true
            })
        ],

Meaning , why is it between ()?

wbigno commented 8 months ago

Im overriding the services config from my wdio.config. My wdio has image compare, timeline report and dev tools. For sauce I need sauce service, don’t need debtools.. so I have always imported the wdio and then set the services to what I need only for that config. Does that answer your question? Is that not what you would recommend? We have been using this approach for 3 years since v4 of webdriverio. On Dec 30, 2023, at 11:08 PM, Wim Selles @.***> wrote: @wbigno Why do you have this in your sauce config [ ('image-comparison', { baselineFolder: join(process.cwd(), visual-regression/${process.env.DOM_BUILD_ENV}/), formatImageName: '{browserName}--{browserVersion}-{tag}', screenshotPath: join(process.cwd(), tmp/${process.env.DOM_BUILD_ENV}/), misMatchTolerance: 0.05, savePerInstance: true, autoSaveBaseline: true, blockOutStatusBar: true, blockOutToolBar: true, autoElementScroll: false, fullPageScrollTimeout: '5000', disableCSSAnimation: true }) ], Meaning , why is it between ()?

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

wswebcreation commented 8 months ago

I don't mind that you overwrite it, but please use the correct syntax, you have the () which is not needed, change it to this

    services: [
        [
            'sauce',
            {
                sauceConnect: true,
                sauceConnectOpts: {
                    proxyTunnel: true,
                    proxy: 'zproxy.*******.com:9480',
                    pac: 'http://pac.zscaler.net/*******.com/proxy.pac',
                    tunnelCainfo: localCaInfo,
                    logger: async function (message) {
                        console.log(message);
                    }
                },
                setJobNameInBeforeSuite: true
            }
        ],
        [
            'image-comparison',
            {
                baselineFolder: join(process.cwd(), `visual-regression/${process.env.DOM_BUILD_ENV}/`),
                formatImageName: '{browserName}--{browserVersion}-{tag}',
                screenshotPath: join(process.cwd(), `tmp/${process.env.DOM_BUILD_ENV}/`),
                misMatchTolerance: 0.05,
                savePerInstance: true,
                autoSaveBaseline: true,
                blockOutStatusBar: true,
                blockOutToolBar: true,
                autoElementScroll: false,
                fullPageScrollTimeout: '5000',
                disableCSSAnimation: true
            }
        ],
        [TimelineService]
    ],