애플 릿 자동화 테스트 의 예제 코드

배경
최근 에 팀 은 애플 릿 자동화 테스트 도 구 를 만 들 계획 이다.할 수 있 는 업무 자 들 이 애플 릿 을 한 번 조작 한 후에 이전의 작업 경 로 를 자동 으로 복원 하고 작업 과정 에서 발생 하 는 이상 을 포착 하여 이번 발표 가 애플 릿 의 기본 기능 에 영향 을 미 칠 것 이 라 고 판단 할 계획 이다.

상술 한 설명 은 간단 해 보이 지만 중간 에 어 려 운 점 이 있다.첫 번 째 어 려 운 점 은 업무 인원 이 작은 프로그램 을 조작 할 때 조작 경 로 를 기록 하 는 것 이다.두 번 째 어 려 운 점 은 기 록 된 조작 경 로 를 어떻게 복원 하 는 지 하 는 것 이다.
자동화 SDK
이 문 제 를 어떻게 복원 하 는 지,물론 공식 적 으로 제공 하 는 SDK:miniprogram-automator를 우선 선택 하 십시오.
애플 릿 자동화SDK는 개발 자 에 게 외부 스 크 립 트 를 통 해 애플 릿 을 조작 하 는 방안 을 제공 하여 애플 릿 자동화 테스트 의 목적 을 실현 했다.이 SDK 를 통 해 다음 과 같은 일 을 할 수 있 습 니 다.
  • 작은 프로그램 이 지정 한 페이지 로 이동 하 는 것 을 제어 합 니 다
  • 애플 릿 페이지 데이터 가 져 오기
  • 애플 릿 페이지 요소 상태 가 져 오기
  • 애플 릿 요소 바 인 딩 이벤트 촉발
  • AppService 에 코드 세 션 주입
  • wx 대상 의 임 의 인터페이스 호출
  • ...
  • 위의 설명 은 모두 공식 문서 에서 나 온 것 이 므 로 뒤의 내용 을 읽 기 전에 먼저 볼 수 있 습 니 다공식 문서.물론 이전에 puppeter 를 사용 한 적 이 있다 면 기본적으로 이음매 없 는 연결 입 니 다.SDK 사용법 을 간단히 소개 하 겠 습 니 다.
    
    //   sdk
    const automator = require('miniprogram-automator')
    
    //          
    automator.launch({
     //               cli   
     // Windows         cli.bat
     // MacOS         cli
     cliPath: 'path/to/cli',
     //     ,           
     projectPath: 'path/to/project',
    }).then(async miniProgram => { // miniProgram   IDE       
     //         index   
     const page = await miniProgram.reLaunch('/page/index/index')
     //    500 ms
     await page.waitFor(500)
     //       
     const element = await page.$('.main-btn')
     //     
     await element.tap()
     //    IDE
     await miniProgram.close()
    })
    SDK 를 사용 하기 전에 개발 자 도구 의 서비스 포트 를 켜 야 합 니 다.그렇지 않 으 면 시작 에 실 패 했 습 니 다.

    사용자 행동 캡 처
    조작 경 로 를 복원 하 는 방법 이 생 겼 으 니 다음은 조작 경 로 를 기록 하 는 어 려 운 문 제 를 해결 해 야 한다.
    작은 프로그램 에 서 는 웹 에서 이벤트 거품 을 일 으 키 는 방식 으로 window 에서 모든 사건 을 포착 할 수 없습니다.예 를 들 어 작은 프로그램 에 서 는 페이지 와 구성 요소 가Page,Component방법 으로 포장 되 어야 하기 때문에 우 리 는 이 두 가지 방법 을 바 꾸 어 전송 을 차단 하 는 방법 을 바 꾸 고 첫 번 째 매개 변수 가event대상 인지 판단 할 수 있 습 니 다.모든 사건 을 포착 하 겠 습 니 다.
    
    //       
    const originPage = Page
    const originComponent = Component
    
    //    Page
    Page = (params) => {
     const names = Object.keys(params)
     for (const name of names) {
     //       
     if (typeof obj[name] === 'function') {
      params[name] = hookMethod(name, params[name], false)
     }
     }
     originPage(params)
    }
    //    Component
    Component = (params) => {
     if (params.methods) {
      const { methods } = params
      const names = Object.keys(methods)
      for (const name of names) {
      //       
      if (typeof methods[name] === 'function') {
       methods[name] = hookMethod(name, methods[name], true)
      }
      }
     }
     originComponent(params)
    }
    
    const hookMethod = (name, method, isComponent) => {
     return function(...args) {
     const [evt] = args //        
     //       event   
     if (evt && evt.target && evt.type) {
      //       
     }
     return method.apply(this, args)
     }
    }
    이 코드 는 모든 이벤트 방법 을 대리 할 뿐 사용자 의 행 위 를 복원 하 는 데 사용 할 수 없습니다.사용자 의 행 위 를 복원 하려 면 이 이벤트 유형 이 필요 한 지 알 아야 합 니 다.예 를 들 어 클릭,길 게 누 르 기,입력 등 입 니 다.
    
    const evtTypes = [
     'tap', //   
     'input', //   
     'confirm', //   
     'longpress' //   
    ]
    const hookMethod = (name, method) => {
     return function(...args) {
     const [evt] = args //        
     //       event   
     if (
      evt && evt.target && evt.type &&
      evtTypes.includes(evt.type) //       
     ) {
      //       
     }
     return method.apply(this, args)
     }
    }
    이벤트 형식 을 확인 한 후에 클릭 한 요소 가 도대체 어디 인지 명확 하 게 해 야 합 니 다.그러나 작은 프로그램 에서 구 덩이 를 비교 하 는 곳 은 이벤트 대상 의 target 속성 에 요소 의 유형 은 없 지만 요소 의 dataset 를 얻 을 수 있 습 니 다.

    요 소 를 정확하게 얻 기 위해 서 는 구축 에 절 차 를 추가 하고 wxml 파일 을 수정 하여 요소 의class속성 을data-className로 복사 해 야 합 니 다.
    
    <!--     -->
    <view class="close-btn"></view>
    <view class="{{mainClassName}}"></view>
    <!--     -->
    <view class="close-btn" data-className="close-btn"></view>
    <view class="{{mainClassName}}" data-className="{{mainClassName}}"></view>
    그러나 클 라 스 를 가 져 오 면 또 다른 구덩이 가 있 습 니 다.애플 릿 의 자동화 테스트 도 구 는 페이지 에 있 는 사용자 정의 구성 요소 의 요 소 를 직접 가 져 올 수 없습니다.사용자 정의 구성 요 소 를 먼저 가 져 와 야 합 니 다.
    
    <!-- Page -->
    <toast text="loading" show="{{showToast}}" />
    <!-- Component -->
    <view class="toast" wx:if="{{show}}">
     <text class="toast-text">{{text}}</text>
     <view class="toast-close" />
    </view>
    
    //        .toast-close     null
    const element = await page.$('.toast-close')
    element.tap() // Error!
    
    //             tagName        
    //            className       
    const element = await page.$('toast .toast-close')
    element.tap()
    그래서 우 리 는 빌 드 작업 을 할 때 요소 에 tagName 을 삽입 해 야 합 니 다.
    
    <!--     -->
    <view class="close-btn" />
    <toast text="loading" show="{{showToast}}" />
    <!--     -->
    <view class="close-btn" data-className="close-btn" data-tagName="view" />
    <toast text="loading" show="{{showToast}}" data-tagName="toast" />
    이제 우 리 는 사용자 의 행동 을 계속 즐겁게 기록 할 수 있다.
    
    //          
    const actions = [];
    //       
    const addAction = (type, query, value = '') => {
     actions.push({
     time: Date.now(),
     type,
     query,
     value
     })
    }
    
    //       
    const hookMethod = (name, method, isComponent) => {
     return function(...args) {
     const [evt] = args //        
     //       event   
     if (
      evt && evt.target && evt.type &&
      evtTypes.includes(evt.type) //       
     ) {
      const { type, target, detail } = evt
      const { id, dataset = {} } = target
      const { className = '' } = dataset
      const { value = '' } = detail // input     ,     
      //       
      let query = ''
      if (isComponent) {
      //          ,          tagName
      query = `${this.dataset.tagName} `
      }
      if (id) {
      // id   ,      id     
      query += id
      } else {
      // id    ,    className     
      query += className
      }
      addAction(type, query, value)
     }
     return method.apply(this, args)
     }
    }
    사용자 의 모든 클릭,입력,리 턴 과 관련 된 작업 을 기 록 했 지만 스크롤 스크린 작업 은 기록 되 지 않 았 습 니 다.페이지 의 onPage Scroll 을 직접 감청 할 수 있 습 니 다.
    
    //          
    const actions = [];
    //       
    const addAction = (type, query, value = '') => {
     if (type === 'scroll' || type === 'input') {
     //               ,    value   
     const last = this.actions[this.actions.length - 1]
     if (last && last.type === type) {
      last.value = value
      last.time = Date.now()
      return
     }
     }
     actions.push({
     time: Date.now(),
     type,
     query,
     value
     })
    }
    
    Page = (params) => {
     const names = Object.keys(params)
     for (const name of names) {
     //       
     if (typeof obj[name] === 'function') {
      params[name] = hookMethod(name, params[name], false)
     }
     }
     const { onPageScroll } = params
     //       
     params.onPageScroll = function (...args) {
     const [evt] = args
     const { scrollTop } = evt
     addAction('scroll', '', scrollTop)
     onPageScroll.apply(this, args)
     }
     originPage(params)
    }
    
    여기 에는 스크롤 작업 기록 이 있 을 때 마지막 작업 도 스크롤 작업 인지 판단 할 수 있 습 니 다.같은 작업 이 라면 스크롤 거 리 를 수정 하면 됩 니 다.두 번 스크롤 하면 한 걸음 에 도착 할 수 있다 고 생각 합 니 다.마찬가지 로 입력 이벤트 도 입력 한 값 이 한 걸음 에 도착 할 수 있 습 니 다.
    사용자 행동 복원
    사용자 작업 이 끝 난 후 콘 솔 에서 사용자 행동 의 제 이 슨 텍스트 를 출력 하고 제 이 슨 텍스트 를 복사 하면 자동화 도 구 를 통 해 실행 할 수 있 습 니 다.
    
    //   sdk
    const automator = require('miniprogram-automator')
    
    //       
    const actions = [
     { type: 'tap', query: 'goods .title', value: '', time: 1596965650000 },
     { type: 'scroll', query: '', value: 560, time: 1596965710680 },
     { type: 'tap', query: 'gotoTop', value: '', time: 1596965770000 }
    ]
    
    //          
    automator.launch({
     projectPath: 'path/to/project',
    }).then(async miniProgram => {
     let page = await miniProgram.reLaunch('/page/index/index')
     
     let prevTime
     for (const action of actions) {
     const { type, query, value, time } = action
     if (prevTime) {
      //              
      await page.waitFor(time - prevTime)
     }
     //         
     prevTime = time
     
     //         
     page = await miniProgram.currentPage()
     switch (type) {
      case 'tap':
       const element = await page.$(query)
      await element.tap()
      break;
      case 'input':
       const element = await page.$(query)
      await element.input(value)
      break;
      case 'confirm':
       const element = await page.$(query)
        await element.trigger('confirm', { value });
      break;
      case 'scroll':
      await miniProgram.pageScrollTo(value)
      break;
     }
     //        ,   5s,         ,          
     await page.waitFor(5000)
     }
    
     //    IDE
     await miniProgram.close()
    })
    여 기 는 사용자 의 조작 행 위 를 간단하게 복원 하 는 것 일 뿐 실제 실행 과정 에서 네트워크 요청 과 localstorage 의 mock 도 포함 되 어 더 이상 설명 하지 않 습 니 다.또한,우 리 는 jest 도구 에 접속 하여,더욱 편리 하 게 사례 를 작성 할 수 있 습 니 다.
    총결산
    어 려 울 것 같은 요 구 는 열심히 발굴 하면 그 에 맞 는 해결책 을 찾 을 수 있다.또한 위 챗 애플 릿 의 자동화 도 구 는 정말 많은 구덩이 가 있 습 니 다.문제 가 발생 하면 먼저 애플 릿 커 뮤 니 티 에 가서 찾 아 볼 수 있 습 니 다.대부분 구 덩이 는 앞 사람 이 밟 았 고 일시 적 으로 해결 할 수 없 는 문 제 는 다른 방법 으로 피 할 수 밖 에 없습니다.마지막 으로 천하 에 bug 가 없 기 를 기원 합 니 다.
    애플 릿 자동화 테스트 에 관 한 예제 코드 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 애플 릿 자동화 테스트 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기