Overview of front-end automated testing [super comprehensive introduction]

insert image description here

Why do you need automated testing

After continuous development, the project will eventually become stable. The introduction of automated testing at an appropriate time can detect problems early and ensure product quality.
Benefits of automation = number of iterations * cost of full manual execution - cost of first automation - number of maintenance * maintenance cost

test

As the last link in the complete development process, testing is an important link to ensure product quality. Front-end testing is generally a late link in the product development process, and it belongs to a higher level in the entire development architecture. Front-end testing is more inclined to GUI features, so front-end testing is very difficult.

testing method

black box testing

Black-box testing is also commonly referred to as functional testing. Black-box testing requires the tester to treat the program as a whole, regardless of its internal structure and characteristics, but to verify that the program works as expected. Black-box testing is closer to the real-world scenario that the user uses, because the internals of the program are invisible to the user.
The following are commonly used test methods in black box testing:

Equivalence Class Division

The equivalence class division is mainly to determine the legal input and illegal input interval to design test cases under the condition of existing input rules.
For example: for example, the website login password must be composed of 6 digits.
Valid equivalence class: 6-digit
invalid equivalence class : digits>6, digits<6, full-width numbers, letters, special characters, etc...

Boundary value analysis

As the name implies, the design of test cases is mainly based on the boundary values ​​of the input and output ranges. The reason is that a large number of errors often occur on the boundaries of the input or output range (programmers tend to make mistakes in these places), and boundary value analysis is generally used in conjunction with equivalence class division, and the boundary of equivalence class division interval is generally the boundary value.

For example: the length of the website login password must be 6-12 digits
Valid equivalence class: digits [6-12]
Invalid equivalence class: digits <6 digits> 12
Boundary value: 6 12

Error speculation, exception analysis, etc.

Black box testing also includes some other testing methods. Since testing is often inexhaustible, how to design test cases to ensure that tests cover as many scenarios as possible not only rely on these summarized methods, but also test testers own talent.

white box testing

White box testing is a test based on the code itself, generally referring to the test of the logical structure of the code. White-box testing is a test conducted on the premise of understanding the code structure, and the purpose is to traverse as many executable paths as possible to obtain test data. There are many white-box testing methods, mainly logic coverage, that is, checking every line of code and every judgment result.
The logic coverage methods are sorted from the ability to find errors mainly in the following categories:

Statement coverage (let the program execute to each line of statements)
decision coverage (let each judgment statement satisfy true or false)
condition coverage (let each condition in each judgment statement take the true or false value)
decision/condition coverage (satisfy both 2 and 3)
Condition combination coverage (every combination of conditions in the judgment statement appears at least once)
path coverage (covers each execution path of the program)

Test classification

According to the bottom-up concept of software engineering, front-end testing is generally divided into unit testing (Unit Testing), integration testing (Integration Testing) and end-to-end testing (E2E Testing). As can be seen from the figure below, the complexity of bottom-up testing will continue to increase, and on the other hand, the benefits of testing will continue to decrease.
insert image description here

Front-end testing model

Unit Testing

Unit testing refers to the testing of the smallest testable unit in a program, and generally refers to the testing of functions. Unit testing mixes programming and testing. Because it tests the internal logic of the code, it uses more white-box testing methods. Unit tests force developers to write more testable code, which is generally much more readable, and good unit tests serve as documentation for the code under test.

Function testability: Functions with high testability are generally pure functions, that is, functions with predictable input and output. That is, the incoming parameters are not modified inside the function, API requests or IO requests are not executed, and other impure functions such as Math.random() are not called.

The biggest feature of unit testing is the fine-grainedness of the test object, that is, the test object is highly independent and low in complexity.

Front-end unit testing

The biggest difference between front-end unit testing and back-end unit testing is that compatibility problems cannot be avoided in front-end unit testing, such as calling browser compatibility APIs and calling BOM (Browser Object Model) APIs, so front-end unit testing Requires running in a (pseudo) browser environment.
As far as the test operating environment is classified, there are mainly the following test schemes:

Based on JSDOM

Advantages: fast, the fastest execution speed, because no browser startup is
required And JSDOM does not implement localStorage. If you need to override it, you can only use a third-party library such as node-localStorage (this library itself has some problems with the judgment of the execution environment).

Based on headless browsers such as PhantomJs

Advantages: Relatively fast, and has a real DOM environment
Disadvantages: It also does not run in a real browser, it is difficult to debug, and there are many project issues. After the release of puppeteer, the author announced that it would no longer be maintained

Use tools such as Karma or puppeteer to call the real browser environment for testing

Advantages: simple configuration, can run tests in real browsers, and karma can run test code in multiple browsers, and at the same time facilitate debugging
Disadvantages: the only disadvantage is that it runs slightly slower than the first two, but in unit tests within acceptable range

Front-end unit testing tools

A lot of test frameworks and related tools have sprung up like mushrooms in the front-end in recent years.

testing platform

karma - A test running platform developed by the Google Angular team. It is simple and flexible to configure and can easily run tests in multiple real browsers.

test framework

mocha - an excellent testing framework developed by Tj, with a complete ecosystem, simple test organization, no restrictions on assertion libraries and tools, and very flexible.
jasmine - very similar to Mocha syntax, the biggest difference is that it provides self-built assertions and Spy and Stub
jest - a large and comprehensive testing framework produced by facebook, the unit testing framework officially recommended by React, with simple configuration and fast running speed. (Note: Can't integrate with Karma)
AVA - The biggest difference from the above test framework is that it is multi-threaded and runs faster.
Others – There are some other front-end testing frameworks, but the similarity is relatively high. It is nothing more than a difference in the integration of tools such as assertions and test piles. If you consider stability and maturity, it is recommended to choose Mocha, which has very high requirements for test running speed. Consider jest and AVA

test aids

Assertion library - Chai If the unit test does not run in the real browser environment, you can simply use node's assert, but it is recommended to use Chai as the assertion library (provides a variety of assertion methods in TDD and BDD styles, and the ecosystem is prosperous).
Test stubs (also known as test doubles) – Sinon, testDouble and other tools provide functions such as test stubs, intercepting simulated requests, and "time travel", which are mainly used to solve "impure functions" (such as testing whether the callback is called correctly, Whether the XHR correctly initiates the request and whether the function behaves correctly after the time delay is the test problem.

Test Coverage Tool

istanbul The basic implementation of istanbul provides tools such as command line, but cannot solve the problem of code compilation and management
istanbul-instrumenter-loader istanbul's Webpack plug-in can solve the problem of code compilation and management and test report output

Other references

chai-as-promise Extends Chai's assertion function on Promise
sinon-chai Extends Chai's assertion function when paired with Sinon
chai-jquery Extends Chai's assertion in UI testing
istanbul's official website introduces how istanbul integrates with multi-testing frameworks and for Typescript Support for other languages
​​Ruan Yifeng - Code Coverage Tool Istanbul Introductory Tutorial introduces the concepts related to code coverage and the simple use of Istanbul with Mocha

Common unit test chestnuts

Consider the following conditions or issues when selecting a frame:

The test needs to be run in a real browser The
test execution needs to be fast enough
The code under test is Typescript, so the use needs to solve the problem of compilation and management It is
convenient for continuous integration

Finally choose the solution using Karma+Webpack+Mocha+Chai+Sion+istanbul-instrumenter-loader.
Project structure:
insert image description here

Karma can be easily integrated with Webpack, you only need to specify the files that need to be precompiled and the compilation tool to be used.

karma.conf.js配置Webpack编译
module.export=funciton(config){
config.set({
    frameworks: ['mocha', 'chai'],
    files: [
  		'tests/setup.js'
	],
    preprocessors: {
      'tests/setup.js': ['webpack']
    },
    webpack: {
      resolve: {
        extensions: ['.js','.ts'],
      },
      module: {
        rules: [{
            test: /\.ts$/,
            loader: 'ts-loader',
            query: {
              transpileOnly: true
            }
          },
          {
            test: /\.ts$/,
            exclude: /(node_modules|libs|\.test\.ts$)/,
            loader: 'istanbul-instrumenter-loader',
            enforce: 'post',
            options: {
              esModules: true,
              produceSourceMap: true
            }
          }
        ]
      },
      devtool: 'inline-source-map',
    },
    // karma-server support ts/tsx mime 
    mime: {
      'text/x-typescript': ['ts', 'tsx']
    },
})

}

There are a few things to pay attention to in the above configuration:

setup.js

        // 导入所有测试用例
const testsContext = require.context("../src", true, /\.test\.ts$/);
testsContext.keys().forEach(testsContext);

Use the require.context() provided by webpack to import all test files uniformly, then karma uses setup.js as the entry to call webpack to compile, and then the test is executed.

mime configuration support for ts/tsx

The order in which the istanbul-instrumenter-loader loader is called must be compiled before ts or babel and other compilation tools are compiled, otherwise it will be inaccurate, excluding the test file itself and the dependency library coverage calculation

 使用enforce: ‘post’,确保loader调用顺序
 使用exclude排除第三方库和测试文件本身覆盖率计算

For other configurations, please refer to karma and webpack, as well as related plugin documentation

pure function test

            /**
 * 验证邮箱
 * @params input 输入值
 * @return 返回是否是邮箱
 */
export function mail(input: any): boolean {
    return /^[\w\-]+(\.[\w\-]+)*@[\w\-]+(\.[\w\-]+)+$/.test(input);
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

describe('验证邮箱#mail', () => {
    it('传入邮箱字符串,返回true', () => {
        expect(mail('[email protected]')).to.be.true;
        expect(mail('[email protected]')).to.be.true;
        expect(mail('[email protected]')).to.be.true;
        expect(mail('[email protected]')).to.be.true;
        expect(mail('[email protected]')).to.be.true;
    });

    it('传入非邮箱字符串,返回true', () => {
        expect(mail('')).to.be.false;
        expect(mail('abc@')).to.be.false;
        expect(mail('@123.com')).to.be.false;
        expect(mail('abc@123')).to.be.false;
        expect(mail('123.com')).to.be.false;
    });
});

Promise or other asynchronous operation testing
Mocha supports asynchronous testing, mainly in the following three ways:

使用async await

                it(async ()=>{
await asynchronous()
// 一些断言

})

Use callback function

                it(done=>{
Promise.resolve().then(()=>{
    // 断言
    done() // 测试结束后调用回调标识测试结束
})

})

return a Promise

                it(()=>{
// 直接返回一个Promise,Mocha会自动等待Promise resolve
return Promise.resolve().then(()=>{
    // 断言
})

})

Test with HTTP request

            function http(method,url,body){
    // 使用XHR发送ajax请求
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

it('method为GET', (done) => {
    const xhr=fake.useFakeXMLHttpRequest()
    const request = []
    xhr.onCreate = xhr => {
        requests.push(xhr);
    };
    requests[0].respond(200, { "Content-Type": "application/json" },'[{ "id": 12, "comment": "Hey there" }]');
    get('/uri').then(() => {
            xhr.restore();
            done();
    });
    expect(request[0].method).equal('GET');
})

Or encapsulate fakeXMLHttpRequest

            /**
 * 使用fakeXMLHttpRequest
 * @param callback 回调函数,接受请求对象作为参数
 */
export function useFakeXHR(callback) {
    const fakeXHR = useFakeXMLHttpRequest();
    const requests: Array<any> = []; // requests会将引用传递给callback,因此使用const,避免指针被改写。

    fakeXHR.onCreate = request => requests.push(request);

    callback(requests, fakeXHR.restore.bind(fakeXHR));
}

it('method为GET', (done) => {
    useFakeXHR((request, restore) => {
        get('/uri').then(() => {
            restore();
            done();
        });
        expect(request[0].method).equal('GET');
        request[0].respond();
    })
})

useFakeXHR encapsulates Sinon's useFakeXMLHttpRequest to implement fake XHR requests

Fake XHR and server - Sinon.JS 官方文档参考
Fake XHR和 Fake server的区别:后者是对前者的更高层次的封装,并且Fake server的颗粒度更大

time-dependent tests

        // 延迟delay毫秒后调用callback
function timer(delay,callback){
    setTimeout(callback,delay);
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

it('timer',()=>{
    const clock=sinon.useFakeTimers()
    const spy=sinon.spy() // 测试替身
    timer(1000,spy) 
    clock.tick(1000) // "时间旅行,去到1000ms后"
    expect(spy.called).to.be.true
    clock.restore() // 恢复时间,否则将影响到其他测试
})

Testing of browser-related API calls

session chestnut

            /**
 * WebStorage封装
 * @param storage 存储对象,支持localStorage和sessionStorage
 * @return 返回封装的存储接口
 */
export function Storage(storage: Storage) {
    /**
     * 获取session
     * @param key 
     * @return 返回JSON解析后的值
     */
    function get(key: string) {
        const item = storage.getItem(key);

        if (!item) {
            return null;
        } else {
            return JSON.parse(item);
        }
    }
}
export default Storage(window.sessionStorage);

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

describe('session', () => {
    beforeEach('清空sessionStorage', () => {
        window.sessionStorage.clear();
    });

    describe('获取session#get', () => {
        it('获取简单类型值', () => {
            window.sessionStorage.setItem('foo', JSON.stringify('foo'))
            expect(session.get('foo')).to.equal('foo');
        });

        it('获取引用类型值', () => {
            window.sessionStorage.setItem('object', JSON.stringify({}))
            expect(session.get('object')).to.deep.equal({});
        });

        it('获取不存在的session', () => {
            expect(session.get('aaa')).to.be.null;
        });
    });
})

Set the chestnut of the title

            /**
 * 设置tab页title
 * @param title 
 */
export function setTitle(title) {
    // 当页面中嵌入了flash,并且页面的地址中含有“片段标识”(即网址#之后的文字)IE标签页标题被自动修改为网址片段标识
    if (userAgent().app === Browser.MSIE || userAgent().app === Browser.Edge) {
        setTimeout(function () {
            document.title = title
        }, 1000)
    } else {
        document.title = title
    }
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

it('设置tab页title#setTitle', () => {
    // 这个测试不是个好的测试,测试代码更改了页面DOM结构
    const originalTitle = document.title; // 保存原始title,测试结束后恢复
    const clock = sinon.useFakeTimers()
    setTitle('test title');
    clock.tick(1000)
    expect(document.title).to.equal('test title')

    setTitle(originalTitle); // 恢复原本的title
    clock.tick(1000)
    clock.restore()
})

React component unit testing

The testing of React components is not much different from other unit tests in essence, but the unit testing of React components is much more complex due to the "browser rendering" problem involved, and React components are in addition to basic UI components (such as Button, Link), etc. Outside of the underlying components, it is actually difficult to keep its "scale" within the "minimum test unit" range, although the complexity of the component can be reduced by "shallow rendering" (no rendering of subcomponents). From the perspective of the scope and significance of unit testing, in fact, most UI component testing is difficult to classify into unit testing. Since "everyone seems to take it for granted", this document still introduces UI component testing as unit testing.

        没有打开盒子时,我们无法决定猫是否还活着。

React is not a black box, so to test React, you must first understand how React renders. Of course, we don't need to understand the source code of React, just know about it.
In a nutshell, it takes the following steps to render a React component into a page.

  1. Calculate the virtual DOM object based on the state

  2. Generate real Dom structure based on virtual DOM

  3. Mount Dom to the page

    But in the test, we don't need to mount the component to the page, just have a Dom environment. If you don't need a complete rendering component, you can even have no Dom environment.

    Enzyme

    Enzyme (enzyme, catalyst) is a React testing tool provided by Airbnb. It provides Shallow Rendering | Full Rendering | Static Rendering to test React components. Test with shallow rendering.

     使用Enzyme进行测试前需要先初始化React适配器,具体配置请查看官方手册
    

Shallow Rendering

Only the outermost structure of the component is rendered, and subcomponents are not rendered
. An example of a complete Shallow rendering test:

            import * as React from 'react'
import * as classnames from 'classnames'
import { ClassName } from '../helper'
import * as styles from './styles.desktop.css'

const AppBar: React.StatelessComponent<any> = function AppBar({ className, children, ...props } = {}) {
    return (
        <div className={classnames(styles['app-bar'], ClassName.BorderTopColor, className)} {...props}>{children}</div>
    )
}


export default AppBar

            import * as React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import AppBar from '../ui.desktop'

describe('ShareWebUI', () => {
    describe('AppBar@desktop', () => {
        describe('#render', () => {
            it('默认渲染', () => {
                shallow(<AppBar></AppBar>) 
            });
            it('正确渲染子节点', () => {
                const wrapper = shallow(<AppBar><div>test</div></AppBar>)
                expect(wrapper.contains(<div>test</div>)).to.be.true
            });
            it('允许设置ClassName', () => {
                const wrapper = shallow(<AppBar className='test'></AppBar>)
                expect(wrapper.hasClass('test')).to.be.true
            });
            it('允许设置其他自定义props', () => {
                const wrapper = shallow(<AppBar name='name' age={123}></AppBar>)
                expect(wrapper.props()).to.be.include({ name: 'name', age: 123 })
            });
        });
    });
});

Full Rendering
renders the real Dom structure of the component. If you need to test native events, you must use this rendering method.

Static Rendering
is similar to the result obtained by the crawler, getting the HTML string of the page and using Cheerio to operate.

The event simulation in Enzyme's shallow rendering mode is not a real event triggering. In fact, it is a "deception" implementation. For example, buttonWrapper.simulate('click') simply calls the function passed to the onClick parameter of the Button component That's it.
Specific description: airbnb.io/enzyme/docs...
Enzyme's many pits: airbnb.io/enzyme/docs...
If setState() is called in an asynchronous operation, the test needs to be asserted in the next clock cycle (similar problems are more many):

/* Simulate a click /
wrapper.find('UIIcon').simulate('click')
expect(wrapper.state('loadStatus')).to.equal(1) // Click to immediately change the loading state to loading
/
in SetState is set in promise.then, so it needs to be asserted on the next clock */
setTimeout(() => { expect(wrapper.state('loadStatus')).to.equal(2) done() }, 0)


Integration Testing

Integration testing refers to the encapsulation of high-level functions or classes exposed by combining and integrating the tested unit test functions on the basis of unit testing, and testing these functions or classes.
The biggest difficulty in integration testing is that the granularity is larger, the logic is more complex, and there are more external factors, which cannot guarantee the controllability and independence of the test. The solution is to use test stubs (test doubles), that is, to replace the sub-function or module that is called, that is, to hide the details of the sub-module and control the behavior of the sub-module to achieve the expected test. (The premise here is that the submodule has been covered by full unit tests, so it can be assumed that the submodule state is known.)

Typescript compiles to Commonjs:

    // 编译前
import * as B from 'B'
import {fn} from 'C'

export function A() { 
    B.fn.name()
    fn.name()
}

export class A1 {
    
}

// 编译后
define(["require", "exports", "B", "C"], function (require, exports, B, C_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    function A() {
        B.fn.name();
        C_1.fn.name();
    }
    exports.A = A;
    var A1 = /** @class */ (function () {
        function A1() {
        }
        return A1;
    }());
    exports.A1 = A1;
});

All exported functions and classes are under the exports object. Since the call is actually called exports.exportName, if you want to Stub, you need the properties under Stub exports. The stub function provided by Sinon allows us to modify any object under an object. Attribute, note that it must be under an object, that is to say, it cannot be directly sinon.stub(fn), only sinon.stub(obj,'fnName'). In ES6, you can export the entire module to a single object through import * as moduleName from 'moduleName ', which can solve the problem of Stub.
Consider having the following module dependencies:
insert image description here

Among them, module A depends on modules B and C, and module B depends on module C. In this case, we can choose only Stub module C. At this time, the C module in module B will also be affected by Stub. The best way is to simultaneously Stub module B and module C.
Chestnut

    import { clientAPI } from '../../../clientapi/clientapi';

/**
 * 通过相对路径获取缓存信息
 * @param param0 参数对象
 * @param param0.relPath 相对路径
 */
export const getInfoByPath: Core.APIs.Client.Cache.GetInfoByPath = function ({ relPath }) {
    return clientAPI('cache', 'GetInfoByPath', { relPath });
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */

import { expect } from 'chai'
import { stub } from 'sinon'
import * as clientapi from '../../../clientapi/clientapi';

import { getInfoByPath, getUnsyncLog, getUnsyncLogNum } from './cache'

describe('cache', () => {
    beforeEach('stub', () => {
        stub(clientapi, 'clientAPI')
    })
    afterEach('restore', () => {
        clientapi.clientAPI.restore()
    })

    it('通过相对路径获取缓存信息#getInfoByPath', () => {
        getInfoByPath({ relPath: 'relPath' })
        expect(clientapi.clientAPI.args[0][0]).to.equal('cache') // 请求资源正确
        expect(clientapi.clientAPI.args[0][1]).to.equal('GetInfoByPath') // 请求方法正确
        expect(clientapi.clientAPI.args[0][2]).to.deep.equal({ relPath: 'relPath' }) // 请求体正确
    })
})

Or use the Sandbox provided by Sinon, which is easier to restore, and does not need to restore each stubbed object separately.

import { rsaEncrypt } from '../../util/rsa/rsa';
import { getNew} from '../apis/eachttp/auth1/auth1';
/**
 * 认证用户
 * @param account {string}
 * @param password {string}
 */
export function auth(account: string, password: string, ostype: number, vcodeinfo?: Core.APIs.EACHTTP.Auth1.VcodeInfo): Promise<Core.APIs.EACHTTP.AuthInfo> {
    return getNew({
        account,
        password: encrypt(password),
        deviceinfo: {
            ostype: ostype
        },
        vcodeinfo
    });
}

/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */
import { createSandbox } from 'sinon'
import * as auth1 from '../apis/eachttp/auth1/auth1'
import * as rsa from '../../util/rsa/rsa'
const sandbox = createSandbox()
describe('auth', () => {
    beforeEach('stub',()=>{
        sandbox.stub(rsa,'rsaEncrypt')
        sandbox.stub(auth1,'getNew')
    })
    
    afterEach('restore',()=>{
        sandbox.restore()
    })
    
    it('认证用户#auth', () => {
        auth('account', 'password', 1, { uuid: '12140661-e35b-4551-84cf-ce0e513d1596', vcode: '1abc', ismodif: false })
        rsa.rsaEncrypt.returns('123') // 控制返回值
		expect(rsa.rsaEncrypt.calledWith('password')).to.be.true
        expect(auth1.getNew.calledWith({
            account: 'account',
            password: '123',
            deviceinfo: {
                ostype: 1
            },
            vcodeinfo: {
                uuid: '12140661-e35b-4551-84cf-ce0e513d1596',
                vcode: '1abc',
                ismodif: false
            }
        })).to.be.true
    })
}

Reference documentation:

        Stubs - Sinon.JS Stub的相关概念和使用
        Sandboxes - Sinon.JS Sandbox(沙盒)的相关概念和使用

End-to-end testing (E2E Testing)
End-to-end testing is the top-level test, that is, treat the program as a complete black box as a user, open the application to simulate input, and check whether the function and interface are correct.
Some issues that need to be addressed for end-to-end testing:

The environment problem
is how to ensure that the environment before each test is "clean". For example, it is necessary to check the performance of the empty list. If the list was added in the previous test, the next test will not be able to get the state of the empty list.
The easiest solution is to call an external script to clear the database, etc. before or after all tests are executed, or it can be solved by intercepting the request and customizing the response (this will lead to high test complexity and not enough "real" ).

Element lookup
If the code changes frequently, the component structure changes frequently, and if you look for the action element based on the DOM structure, then you will fall into the hell of maintaining selectors. The best practice is to use the test-id method, but this method requires the cooperation of developers and testers to define semantic test-ids on actionable elements.

Operation waiting
, such as interface changes caused by asynchronous network requests, or interface animations, will make the timing of obtaining operation elements unknown. The solution is to wait until the listening request completes and the desired element is successfully fetched.

Use operations instead of assertions
You should rely more on operations than assertions. For example, if an operation depends on the existence of element A, you do not need to "judg whether element A exists in the page", but should go to "directly obtain element A and operate", because if element A does not exist, it will definitely not be obtained. , the operation after the assertion will be meaningless, so you can use the operation directly instead of the assertion wait function.

Finally: [May help you]

These materials should be the most comprehensive and complete preparation warehouse for friends who are considering advanced [software testing] skills. This warehouse has also accompanied me through the most difficult journey, and I hope it can also help you.
insert image description here

Follow my WeChat public account [Program Yuanmuzi] to get it for free

My learning exchange group: 644956177 There are technical experts in the group to communicate and share~

Guess you like

Origin blog.csdn.net/Xsk215/article/details/117160879