안녕하세요 Selenium - Web Driver ( Node.js Version ) 에서 SendKeys 에 대해서 간단하게 알아 보려고 합니다.

 

처음에 SendKeys 에 대해서 사용하게 되는 경우 대부분  자동화 도중에 어떠한 값을 "입력" 하게 되는 경우에

 

사용하게 되면서 시작 하게 될 것 같습니다.

 

// DOM select targetElement
let targetElement = driver.findElement(By.css('div .target'));

// targetElement input String 
await targetElement.sendKeys('Automation Input');

하지만 위와같은 방식 말고 다른 방법으로도 사용이 가능합니다.

 

Automation Copy and Paste with SendKeys 

// DOM select targetElement
let targetElement = driver.findElement(By.css('div .target'));

// targetElement input String 
await targetElement.sendKeys('Automation Input');

// Copy and Paste One Take
await targetElement.sendKeys(Key.CONTROL,'a'); // All Select String
await targetElement.sendKeys(Key.CONTROL,'c'); // Block String to Copy 
await targetElement.sendKeys(Key.CONTROL,'v'); // Paste

뭔가 해당 사항만 봤을 때는  'Automation Input' 이 입력되면 Automation Input 이 들어가고

앞에 Control 값이 있을때 해당 값과 키 값을 같이 누르는 것처럼 보입니다.

 

하지만 실제로는 어떻게 동작하는지 알면 왜 위와 같이 동작하는지 이해하기 편합니다.

 

위의 예시코드는 WebElement.sendKeys 였지만 실제로는 Actions 객체로도 가능한데요

이번 포스팅에서는 WebElement.SendKeys 지만 Actions의 SendKeys 도 비슷한 부분들이 많아

우선 Actions의 SendKeys  부터 간단하게 열어 보았습니다.

 

// Selenium github 

https://github.com/SeleniumHQ/selenium/blob/master/javascript/node/selenium-webdriver/lib/input.js#L572

Selenium 은 오픈소스 이다 보니까 코드 가 공개 되어있는데요

 

SendKeys 의 구현체를 발췌 해 보았습니다.

sendKeys(...keys) {
    const actions = [];
    for (const key of keys) {
      if (typeof key === 'string') {
        for (const symbol of key) {
          actions.push(
              this.keyboard_.keyDown(symbol),
              this.keyboard_.keyUp(symbol));
        }
      } else {
        actions.push(
            this.keyboard_.keyDown(key),
            this.keyboard_.keyUp(key));
      }
    }
    return this.insert(this.keyboard_, ...actions);
  }

1. sendKeys(...keys) -> ...keys 로 spread 연산자 입니다. 들어오는 모든 키들을 파라미터로 받습니다.

   이부분이 이해가 안될수 있는게 

function sendKeys(keys){
	console.log(keys);  // automation
}
sendKeys('automation');

이와같은 형태 아닌가? 라고 잠깐 착각 할수 있습니다. 하지만 Spread Operator 를 사용하게되면 조금 이야기가 달라 질 수 있습니다.

function sendKeys(...keys){
	console.log(keys); // ['automation']
}
sendKeys('automation');

위와의 차이점은 배열(Array) 로 들어온다는 점입니다. 이 뜻은  

A, automation 과같이 입력이 된다는 뜻이 됩니다.

function sendKeys(...keys){
	console.log(keys); // ['CONTROL_A','automation']
}
sendKeys('CONTROL_A','automation');

미리 정의해둔 컨트롤(Control) 이나 쉬프트 (Shift) 값이 들어왔을때 조합키 형태로 사용할 수 있도록 

 

여기에서 잠깐 보고 가야 할 부분이 위에 Key.CONTROL 은 lib/input 에 선언이 되어있기 때문에 사용하기 위해서

 

코드의 최상단에

const { Key } = require('selenium-webdriver/lib/input');

로 가져오신 후 사용하시면 됩니다.

/lib/input 선언부

위와같이 되어있기 때문에 사용하고 싶은 부분들을 사용하시면 됩니다.

 

이제 SendKeys 에서 입력 키들을 배열로 받을수 있도록 처리가 되어 있습니다. 

 

간단하게 CONTROL 만 선언해서 돌려 본다면

 

var Key = {
	CONTROL : '\uE009'
}
function sendKeys(...keys){
   const actions = [];
	 for(const key of keys){
		if(typeof key==='string'){
	     	for(const symbol of key){
				console.log(`symbol ${symbol}`);
            }
        }
		else{
		   console.log(`not Symbol ${key}`);
        }
    }
}

sendKeys(Key.CONTROL,'automation');

테스트 결과

보시면 CONTROL 부분은 심볼로 처리되고 그 외에 있는 문자열들은 따로따로 나눠서 되어있습니다.

미리 선언해놓은 심볼들은 내부적으로 다시 연결하여서 그에 맞는 조합키를 입력 합니다. (해당 내용은 이번 포스팅에서는 제외하고 하겠습니다.) 

 

그리고 해당 값들을 통해서 KeyDown 과 KeyUp을 사용하여 진행합니다.

 

"Automation" 이 파라미터로 넘어간다면

 

A Key Down 

A Key Up

u Key Down

u Key Up 

.... 이 순서로 진행이 된다고 볼수 있습니다.

 

그래서 테스트 해 보았습니다. 

 // 첫번째 요소에 Shift 키와 함께 
 await targetElement.sendKeys(Key.SHIFT,'automation');
 // Result : AUTOMATION
 
 // 두번째 요소에 소문자 입력시 
 await subtargetElement.sendKeys('automation');
 // Result : automation
 

위처럼 현재 Element 에 조합키 + 소문자 입력후 다른 Element 에 automation 만 입력시 어떻게 될지 확인해보았을 시

 

입력중인 위젯에 대해서는 Shift키 심볼로 인해 keyDOWN 상태가 유지되어 대문자로 나오지만

 

새롭게 지정된 요소에서는 해당 키 내용이 없기 때문에 소문자가 그대로 나타납니다.

 this.sequences_ = new Map([
      [this.keyboard_, []],
      [this.mouse_, []],
    ]);
	
    insert(device, ...actions) {
    this.sequence_(device).push(...actions);
    return this.sync_ ? this.synchronize() : this;
  }

위의 sendKeys 마지막 부분 요소에 보면 insert하는 부분이 있게 되는데요 위의 코드는 Selenium 자체코드입니다.

 

Actions.SendKeys 의 경우 관련된 액션과 키 값들을 정리 후 한꺼번에 리턴합니다.  

 

Actions의 경우 perform 이 호출될시 리턴된 액션들을 순차적으로 실행하는 형태이기 때문에 그때 순차적으로 실행됩니다.

 

WebElement의 SendKeys 의 경우

 

async sendKeys(...args) {
    let keys = [];
    (await Promise.all(args)).forEach(key => {
      let type = typeof key;
      if (type === 'number') {
        key = String(key);
      } else if (type !== 'string') {
        throw TypeError('each key must be a number of string; got ' + type);
      }

      // The W3C protocol requires keys to be specified as an array where
      // each element is a single key.
      keys.push(...key.split(''));
    });

    if (!this.driver_.fileDetector_) {
      return this.execute_(
          new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
              .setParameter('text', keys.join(''))
              .setParameter('value', keys));
    }

    keys =
        await this.driver_.fileDetector_.handleFile(
            this.driver_, keys.join(''));
    return this.execute_(
        new command.Command(command.Name.SEND_KEYS_TO_ELEMENT)
            .setParameter('text', keys)
            .setParameter('value', keys.split('')));
  }

위와 같은 형태를 띄고있는데요 Actions 과 유사한점이 있지만 다른점이 있다면 바로 Command 를 통해서 실행한다는점입니다.

눈에 띄는 것은 맨 첫줄에 Promise all 부분인데요 들어오는 키 형태중에 조합형태로 Promise 를 리턴하는 경우가 있을 경우에 대비해서 만들어 놓은 것으로 보입니다.

 

위에서 Shift를 눌러놓은 상태에서 automation 시 AUTOMATION 으로 나온후 타 요소에 갈땐 다시 SHIFT가 없는 상태였는데요

 

한 요소에서 같이 될 경우

        await targetElement.sendKeys(Key.SHIFT,'automation');  // AUTOMATION
        await targetElement.sendKeys('ppap'); // ppap

위와같이 보시면 Commander에서 실행 후 다시 초기화가 되는형태로 보여집니다.

 

마지막 궁금했던 테스트입니다.

 await targetElement.sendKeys(Key.CONTROL,'a',Key.CONTROL,'c',Key.CONTROL,'v','v');
 
 await targetElement.sendKeys(Key.CONTROL,'a','c','v','v');
 

그럼 한줄로 그냥 할수 있을거같은데 어떻게 동작하나 보았습니다. 

실제로 이번에 포스팅한 계기는 복사 붙여넣기 자동화 케이스를 만들다가 작성하게 된 경우입니다.

그렇기 때문에 on-copy 와 on-paste 의 이벤트가 Callback 되는지에 중점을 두게 되는데

 

위의 2케이스 전부 콜백은 동작합니다.  v를 한번 더 넣은 경우를 붙여넣기가 블록상태에선 블록만 해제되니까 테스트용으로 두번 넣었습니다. (붙여넣기 확인용)

 

1번 케이스의 경우 결과가 c 로만 나타나게 됩니다.

Ctrl 누르고 a -> 전체 블록 지정    ( 중요하게 보아야 할점은 KeyDOWN  이라는 점 )

다시 Ctrl 누르고 c  ( 한번 더 눌렸기 때문에 keyDOWN Ctrl 이 UP이 되고 c만 입력 )

다시 Ctrl 누르고 v  ( 다시 누르면서 붙여넣기 )

 

결과 : 몇몇 Text 관련 Element 에서는 focus를 주고 난 후에 입력이 되는 경우가 있어서 저는 위처럼 focus를 주고 입력합니다.   

 

긴글 읽어주셔서 감사합니다!!

  // focus in Textarea
        await this.driver.executeScript('arguments[0].focus();',targetElement);
        await targetElement.sendKeys(Key.CONTROL,'a','c','v');

 

안녕하세요 


자사에 있는 제품중인 UI 제품군 중 TOP라는 웹 프론트앤드 프레임워크 의 QA를 맡고 있습니다.


우선 자동화에 대한 설명에 앞서 TOP 에 대한 설명을 제가 직접 할순 없기에 오피셜로 있는 링크를 올립니다.



해당 TOP 에 대한 공식적인 소개

https://tmaxos.com/index#TOP

(소개 페이지였었지만 지금은 TOP로 개발한 홈페이지가 되었습니다) 


기본적으로 Tool + web framework 형태인데


오늘 포스팅 할 내용은 web framework 에 대한 자동화 이야기 입니다. 


IDE라고 하는 tool 자체에 대한 자동화 이야기는 다음에 작성 할 예정입니다.


비즈니스 프레임워크 중에 는 이례적? 이라고 해야할까요 최신 웹 프레임워크의 트렌드를 가진 프레임워크 입니다.

(SPA,Router,Controller,CSR... 등등)


또한 사용자가 만들어야 할 html 코드를 최소화 하여 Data 형태 만  정하게 되면 화면에 나타날수있게 도와줍니다. 


해당 프레임워크에서는 위의 링크와 같이 화면을 구성하기 위한 수십개의 컴포넌트와 컨테이너 등을 제공하게 되는데


그럼과 동시에 해당 컴포넌트들은 크로스 브라우징 형태로 제공이 되기 때문에 지원하는 브라우저가 많아지는 경우


중복하여 확인하여야 하는 작업 이 너무 많아지게 됩니다.


그렇기 때문에 해당 테스트를 자동화 하기 위한 Selenium WebDriver(Node.js)  Jenkins 를 사용하여서 하나의 


Selenium 프로젝트에서 여러 운영체제 및 브라우저(Chrome,FireFox,Internet Explorer, Opera, IOS Safari, Android ) 에 


대한 테스트를 자동화를 하여야 합니다.



이와 관련하여서는 기존에 포스팅 하였던 자료가 있지만 좀더  구조적으로 설명 하며 개요 부터 설명 하도록 하기위해 


다시 작성하였습니다.


// 기존 자동화 링크 

https://ipex.tistory.com/entry/Jenkins-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A7%81%ED%81%AC-%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%97%B0%EB%8F%99-3-%EB%B2%88%EC%99%B8-Iterative-Test-Build-Steps-%EC%82%AC%EC%9A%A9%EC%97%86%EC%9D%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A7%81%ED%81%AC%EC%97%90-%EC%BC%80%EC%9D%B4%EC%8A%A4-%EC%8B%A4%ED%96%89-TestCase-Execution-into-TestLink-without-Iterative-test-build-steps-on-JENKINS



프론트 프레임워크와 기존 웹 페이지 자동화의 차이점


  기존의 화면 (페이지가 만들어져 있는) 에 대한 자동화와 프론트 프레임워크 에 대한 자동화는 차이가있습니다.


            화면 자동화의 경우 A페이지 (로그인 페이지) 일 때




아이디 창에 대한 E2E 테스트가 진행된다고 하면 개발할때 의 스펙에 맞게만 테스트가 진행되어야 합니다.


ex : 50자 최대 일 경우 (50자이상입력이 되는지 )

ex : id만 입력 후 로그인 시 "비밀번호를 입력해주세요" 메시지가 하단에 출력되는지 .

ex : 아이디 입력 후 "엔터" 키 입력 시  "비밀번호" 창으로 이동이 되는지 


해당 화면은 "로그인페이지" 에만 존재하기 때문에 해당 케이스 또한 로그인 페이지에서만 유효한 케이스라고 볼수 있습니다.


하지만 "아이디 입력창" + "비밀번호 입력창" + "로그인 버튼" 이 한개의 컴포넌트라고 보게 됐을경우


<naverLogin-tag> 와 같은 형태로만들어지는 컴포넌트라면 이야기가 달라집니다. 

get함수를 통해서 현재 id pw 가 {id: "",pw:""} 받을수도 있고 하나의 컴포넌트로서 기능적인 면이 들어가기 때문입니다.


컴포넌트를 테스트하는 것과 웹 페이지를 테스트 하는 것은 다르다 


TOP에서는 이런 형태로 수십개의 컴포넌트를 제공하고 있습니다. 단 하나의 태그와 원하는 데이터 형태 로 


사용자가 작성하는 html 태그가 최소화 되는 형태로 제공됩니다.


그렇기 때문에 QA 입장에서는 위젯, 레이아웃,컨테이너가 있을 경우 각각 의 모든 케이스가 TC(테스트케이스) 가 됩니다.


레이아웃은 위젯을 배치하는 컴포넌트이고 위젯은 레이아웃에 배치되는 컴포넌트 컨테이너는 데이터를 표현하기 


위한 컴포넌트 라고 봤을때


A레이아웃  + B 위젯  + C 컨테이너만 놓고 봐도 세개의 컴포넌트에서만 케이스가 무수하게 나옵니다.


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


A레이아웃 안에 C컨테이너를 배치시 화면에 렌더링 되는지

A레이아웃 안에 B위젯을 배치시 화면에 렌더링 되는지

A레이아웃 안에 B + C 컴포넌트가 같이 있을시 같이 렌더링이 되는지

.....


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


그런데 문제는 하나하나 다 컴포넌트 형태이기 때문에 


각각이 제공하는 API 가있고 속성이 있습니다. 


A레이아웃 - C 컨테이너로 테스트하는데 


C컨테이너에 자체 속성이 20개 API 가 20개 일 경우 단순 무식하게 TC(테스트케이스) 를 만든다고 하면 이 숫자 또한 만만치 않게 됩니다.


그래서 원칙적으로는 페어와이즈 테스트 기법을 통해서 케이스를 추출 + 사용자들에 의해 발생하는 회귀테스트 를 우선 작성하고 이에대한 자동화를 진행하게 됩니다.




SeleniumWebDriver (Node.js) 를 사용하는 이유


  Selenium Webdriver Node.js 를 사용한 이유는 우선적 으로 말씀드리자면 JavaScript를 좀 더 많이 쓰기 때문이였습니다. 

하지만 단순히 이런 이유로 사용하기에는 자동화를 하기 위해 좀더 찾아 본 결과로는 SeleniumWebDriver 를 최초의 만들었던 제작자 가 기존의 JAVA 등의 버전에서 현재 js진영으로 넘어와서 새로 프로젝트를 하고 있기 때문이다.(링크를 하려 하였으나 아직 못찾아서 찾는대로 링크하겠습니다.)



[셀레니엄hq 공식 홈페이지]

https://www.seleniumhq.org/download/



Node.js 의 진영은 보시다시피 메이저 버전이 업된 4.0.0 alpha버전임을 확인 할수 있으며  npm 다운로드 수 또한 매우 높아 해당 셀레니움으로 사용하게 되었습니다. 결론적으로 사용하게 된 이유는


정리를 하자면 

  1. 가장 버전이 높은 (활발하기때문)

  2. 사용하던 js언어와 호환성

  3. Node 에서 할수 있는 기능 (xml포매팅 , 파일 관리 등) 을 같이 사용 가능 (리눅스,윈도우 세팅의 편리)

  4. E2E 에서 여러 브라우저를 직접 실행하여 사용가능 ( 하나의 테스트프로젝트를 통해 여러 브라우저에서 테스트 가능 ) 


이외의 알아본 lib 들은 


phantom.js && capser.js , zombie.js slimer.js , webdrivero.io, Cucumber.js , protactor, 

nightwatch.js htmlUnit,Guitar,Cypress.io,uitest, uirecorder,testCafe(*typeScript) 


정도 나열할수 있지만 이러저러한 이유로 사용하지 않았습니다.

 (기본 베이스가 셀레니움이거나, 크로스브라우징에 적합하지않음 등등)



[NPM 다운로드 수 및 이슈]

위의 사진만 비교해봐도 (2018-11-05) selenium-webdriver를 선택 한 점을 찾을수 있다.



SeleniumWebDriver 와 Jenkins 를 사용시 이점 


정확히는 소스코드를 Git으로 관리하게 되는데 이것을 이용한 사용에 대한 이점도 충분히 있습니다.


테스트 자동화 라는게 단순 자동화 를 해주는 SeleniumWebDriver만 이 아니라 일련의 작업

( 최신의 테스트 코드로 테스트를 진행 하고 결과를 리포팅 하는 작업 까지 )


자동화 프로세스




SeleniumWebDriver 코드 작성 



isDisplayDOM 사용 


Chrome브라우저의 경우 속도가 매우 빠릅니다. 타 브라우저에 비해서 그렇기 때문에 단순히 DOM Select 하고 클릭하고

다른 작업으로 넘어 가도 문제가 없지만 IE의 경우에는 이속도가 보장이 되지않는 경우가 많습니다.

(자동화 작업중 트러블 슈팅은 거의 IE에서 일어납니다. ㅜ) 


그렇기 때문에 자체적으로 테스트할 위젯 (클릭하거나 조작할 DOM Element) 를 가져올때 isDisplayDOM 이라는 함수를 만들어서 사용하게 됩니다. 




let startIdx=0;
let retTarget = await this.isDisplayDOM(id);
let tbody = await retTarget.findElement(By.css('table > tbody'));
let totalRowArray = await tbody.findElements(By.css('tr'));


isDisplayDOM 구현체


구현체는 생각보다 단순합니다. driver.wait 라는 API 를 이용해서

until(Util 관련 모듈 입니다.) 을 이용하여서 elementLocated  대상 element가 브라우저 DOM에 붙을때까지 기다립니다.


뒤에 maxWaitTime 같은 경우는 QA가 최대 대기 시간을 작성해놓습니다. 저는 2초 정도로 하였습니다. 2초안에 DOM 에 붙지 않는 다면 해당 테스트 케이스 및 시나리오는 실패로 간주합니다.

(2018-11-20 ) 해당 함수에는 아직 selector 처리가 없네요 id또는 tag , selector로 가져올수 있기 때문에 이부분에 대한처리도 같이 해줘야

진정한 모듈함수 


topqa.prototype.isDisplayDOM = async function(targetId){
let maxWaitTime = 2000;
try{
// let target = await this.driver.findElement(By.id(targetId));
let target = await this.driver.wait(until.elementLocated(By.id(targetId)),maxWaitTime);
if(targetId === await target.getAttribute('id')){
console.log("checked DOM is : ",targetId);
return await target;
}
else{
return false;
}
}
catch(err){
console.warn("isDisplayDOM Error : ",targetId)
console.error(err);
}
}; // isDisplayDOM



해당 과 같이 작성해서 사용하게 되면  DOM 뜨고 나서 진행되기 때문에 중간에  DOM 을 캐치하지못하여 종료되는 오류는 막을수 있습니다.




해당 최신 코드는 


https://github.com/lgance/selenium_tmax/blob/master/topqaModule/autoManager.js


에서 보실수 있습니다!! ( 2019년 에 퇴사를 하여 최신화를 진행하진 않습니다.)





안녕하세요 셀레니움 JS를 쓰다보면 단순하게 문제가 되는 부분이 있습니다.

 

모든 API 들이 전부 비동기 방식이기 때문인데요

 

A,B,C의 WebElement를 가져올건데

 

findElements 로 가져오게되면

 

Promise <Pending> (Array) 로 가져오게 됩니다. 

 

만약에 가져와서 A,B,C에 대한 작업을 순서대로 해야한다면?

 

let webElements = await driver.findElements(By.css('.webElements'))

 

로 가져온 후에   (사실 가져오는 예시도 await가 붙어있어 망정이지 

 

그냥 let webElements = driver.findElements(By.css('.webElements')) 로 하게되면 이 또한 동작하지 않습니다.

 

Promise<pending> 값이 나옵니다.

 

순환하면서 작업한다고 할시

 

webElements.map( (item,index)=>{

 

});

 

 

webElements.forEach( (item,index)=>{

 

});

 

결과는???

 

X 동작하지않습니다.     driver.findElements 의 반환값이 Object이기 때문이죠

 

프로토타이핑을 합시다.

 

Array.prototype.map.call(webElements, (item,index) =>{

});

Array.prototype.forEach.call(webElements,(item,index) =>{


});


결과는?

넵 동작합니다. 하지만 돌려보시면 알겠지만 문제가 하나 생깁니다.

 

console.log("Before");

Array.prototype.map.call(webElements, (item,index) =>{
        // do thing
});

console.log("Start");

시에 해당 작업의 결과는 Before -> Start -> do thing 이 됩니다.

 

아 ! 안에서 async 하게 해줘야 되는데 그쵸?

 

 

console.log("Before");

await Array.prototype.map.call(webElements, async (item,index) =>{
        // await do thing
});

console.log("Start");


결과는요??

Before -> Start - > await do thing 입니다. 

 

왜인가 하면

 

async 함수와 await는 잘 썻습니다

 

하지만 이 반복 자들이 매번 반복 할때마다 반환되는 Promise에 대한 then 처리가 없기 때문이에요 

 

함수를 async 하게 해서 내부적으로 await 하는건 내부적으로 반환된 item 에 대한 비동기 API 사용시 해당 결과를

 

then 하는 것일 뿐 실제로 저 반환되는 item에 대한 Promise가 없기 때문이죠  (프로미!)

 

해결법이 있습니다.

 

ES9 에서 나온 해결법 부터 말씀드리겠습니다.

for await of 문입니다.

console.log("Before");    for await (const item of webElements){             // do thing [item]    }  console.log("Start"); 

 

결과는요??

 

Before ->  await do thing -> Start 입니다. 

 

드디어 잘나오네요 for await of문은 비동기 Iterator 를 위해서 매 순환마다 Promise로 받기 때문에

 

순서 보장 및 동기적 보장을 해줍니다. 하지만 ES9기준이니 Node에서는 10버전 부터 사용 가능합니다.

 

 

reduce를 활용합니다.

 console.log("Before");  await Array.prototype.reduce(webElements,async (acc,item,index)=>{            const nextItem = await acc;           return nextItem;  },Promise.resolve());  console.log("Start");  

 

 

 

리듀스의 특징 중 순환 마다 누적값?을 받아오고 초기값을 지정할수 있다는 점이 있는데요

 

이 특징을 이용해서 초기값을 Promise.resolve() 로 받게 됩니다. 그러면 첫번째 초기값을 리졸브로 시작하며

 

인덱스를 받게 되는데

 

그떄 await로 받으면 (then 으로 받아도 됩니다.)

 

webElement, (acc,item,index)=>{
        acc.then( (nextItem)=>{

        })

 .catch( (err)=>{console.error(err);};

},Promise.resolve());

 

와 같은 느낌으로 받을수도있지만 결국 또 콜백 헬;;; 

 

그렇기 때문에 위의 async await를 쓰도록 합시다.

 

위의 상황을 다시 한번 말씀드리면 첫 인덱스는 Promise.resolve 로 받게 되고

 

인덱스 아이템이 넘어오는 Promise를 await acc 로 기다리면서 받습니다. (동기적)

 

그리고 그 내부 코드에서 비동기 코드는 다시 await를 쓰고 아닌경우는 평소와같이 씁니다.

 

그리고 결과 값을 다시 누적하기 위해 던져줍니다.

 

reduce에 대한 자세한 설명은 밑의 블로그에 있는데

정말  설명이 끝내줍니다. 

// 제로초 블로그 

https://www.zerocho.com/category/JavaScript/post/5acafb05f24445001b8d796d

 

 

 

 

 

 

 

 

 

 

 

같은 코드로 IE11을 테스트할때 UnsupportedOperationError 에러가 나타납니다.


[package.json]

iedriver: 3.9.2

selenium-webdriver: 4.0.0-alpha.1



[Node.js] 10.8.0

[npm ] 6.2.0


IE Browser - IE 11

해당 에러가 났던 코드입니다.


let subMenuList = await this.currlnbMenu.findElements(By.css('ul li'));

await Promise.all(subMenuList.map(async (item,index)=>{
let topSubMenuTextTag = await item.findElement(By.css('.top-menu_text'));

// 이부분에서 에러가 나타납니다.
let targetText = await topSubMenuTextTag.getText();
// 이부분에서 에러가 나타납니다.
if(clickSubMenuText.toLowerCase()===targetText.toLowerCase()){
console.log("find It");
this.currlnbSubMenu = item;
this.currlnbSubMenuText = targetText;
await topSubMenuTextTag.click();
return item;
}
else{
console.log("[subMenuSelect] Looking for element : " ,clickSubMenuText );
console.log("[subMenuSelect] Checking for element : " ,targetText );
console.log('');
}
}));


정확히 나는 부분은 Promise.all 안에 async map 순환 까지는 문제가 없으나 


순환되는 item 파라미터로 


item.getText , item.getAttribute('innerText') 등의 작업을 하게되면 위의 알수없는 미지원에러가 나타납니다.


그래서 해당 코드를 수정했습니다.


let subMenuList = await this.currlnbMenu.findElements(By.css('ul li'));
let length = subMenuList.length;
for(let i=0;i<length;i++){
let topSubMenuTextTag = await subMenuList[i].findElement(By.css('.top-menu_text'));
let targetText = await topSubMenuTextTag.getText();
if(clickSubMenuText.toLowerCase()===targetText.toLowerCase()){
console.log('find It');
this.currlnbSubMenu = subMenuList[i];
this.currlnbSubMenuText = targetText;
await topSubMenuTextTag.click();
break;
}
else{
console.log("[subMenuSelect] Looking for element : " ,clickSubMenuText );
console.log("[subMenuSelect] Checking for element : " ,targetText );
console.log('');
}
}


// Promise.all 과 map 의 합작으로 인해서 오류가 나는것으로 추측 하여 일반 for 문으로 변경하여 사용


안녕하세요 네이버 홈페이지가 변경되었습니다.


기존의 예제로 제공하던


// 네이버 로그인 자동화

http://ipex.tistory.com/entry/Selenium%EC%9B%B9%EC%9E%90%EB%8F%99%ED%99%94-Naver-Login-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%B3%80%EA%B2%BD%EC%97%90-%EB%94%B0%EB%A5%B8-%EC%98%88%EC%A0%9C-%EB%B3%80%EA%B2%BD-%EC%95%88%EB%82%B4?category=770641


가 동작하지 않게 되었습니다.


혹시나 하는 마음에 돌려 보았으나




음 실패 합니다. !!


예전에 작성된 코드를 확인 해보니


// input값 가져옴
let inputId = await driver.findElement(By.css('input#id'));


여기서 못찾는거 같아요 왜냐!

변경되었거든용 페이지가 js로 확인해봐도 안보이는점이 확인되었네요 


그러면 로그인 하기전에 앞에 NAVER 로그인 을 눌러줘야 될거같아요 


눌러주는거야 어렵지 않으니 눌러 줍시다.


document.querySelector("div#account .lg_local_btn").click();  (NAVER 로그인 버튼)


로 누르면 되네요


그 다음 페이지에 id와 pw를 입력 하면 될거같아요 


주석 관련해서는 전부 지우도록 하겠습니다.


로그인 버튼만 해서 넘어가고 나서는 기존의 있던 id 와 pw submit Button 을 그대로 사용한거 같습니다.


코드 그대로 사용해도 되겠네요


앗.. 네이버에서 


비밀번호 자동방지 입력 공간을 만들어서 더이상은 진행이 안될것 같습니다.


CAPTCHA

를 도입했네요 네이버에서 


위키 - 

CAPTCHA는 HIP 기술의 일종으로, 어떠한 사용자가 실제 사람인지 컴퓨터 프로그램인지를 구별하기 위해 사용되는 방법이다.


라고하네용

이것은 기존의 텍스트와 이미지를 일그러뜨린 형태로 변형한 후 인식 대상이 변형된 이미지로부터 기존 이미지를 도출해 낼 수 있는지를 확인하는 방식의 테스트이다.


현재 저로서의 능력 밖에 문제에 직면했네용;


자동방지 이미지는 


document.querySelector("img#captchaimg");


로 가져올수 있고 해당 이미지의 src의 뒷부분에 보면 key 값이 있습니다. 이 key 값을 입력해줘야지 로그인이 될거같았


는데 그것도 아닌거같네요



새로고침 시 마다 & 뒤에 숫자가 계속 붙네요 ㅜ



예제를 다른 걸로 .. 해야겠어요 








안녕하세요 기존에 Windows 7  에서 


selenium-js를 통해서 네이버 자동 로그인을 구현해서 예제로 놓았었는데요


// 네이버 자동로그인 windows 7 Chrome 66 Selenium-JS 4.0.0alpha

http://ipex.tistory.com/entry/webUI-Naver-Login-seleniumJS-iframe-%EB%8C%80%EC%B2%98-%EB%B0%A9%EC%95%88?category=770641


// 네이버 자동로그인 windows 7 IE 11 Selenium-JS 4.0.0alpha

http://ipex.tistory.com/entry/webUI-Naver-Login-seleniumJS-iframe-%EB%8C%80%EC%B2%98-%EB%B0%A9%EC%95%88-IE%EB%B2%84%EC%A0%84?category=770641


// 네이버 변경 되는 로그인 페이지 URL

https://m.blog.naver.com/PostView.nhn?blogId=naver_diary&logNo=221320011665&proxyReferer=



네이버 로그인 페이지가 보안성 강화를 통해서 변경 된다고 합니다.


자동화의 단점이 이렇게 나타나네요 유지보수를 해야할 시점이 이렇게 빨리 찾아 오다니요


네이버 로그인 페이지가 바뀌는 것에 맞춰서 8월에 다시 예제를 작성해서 올리도록 하겠습니다.


감사합니다.


일이 바빠 포스팅 하기가 너무 힘들지만 그래두 열심히 하려고 하고있습니다. ㅜㅜ

 

지난 포스팅때 만들어 놓은 Naver Login  - selenium-JS ( iframe 대처 방안 - IE 버전) 이 있는데요

 

// naver 자동 로그인하여서 최근 메일 가져오기  - IE 

http://ipex.tistory.com/entry/webUI-Naver-Login-seleniumJS-iframe-%EB%8C%80%EC%B2%98-%EB%B0%A9%EC%95%88-IE%EB%B2%84%EC%A0%84?category=770641

 

해당 소스코드가 Windwos 10 에서 같은 드라이버와 같은 브라우저 버전 같은 npm Node.js 버전을 사용하여도

 

동작하지 않습니다.

 

??

 

일단 멈춰지는 소스코드는 await loginBtn.click(); 입니다.

여기서 클릭이 안됐는데 다음 DOM 찾고 객체 찾고를 반복 하다보니

 

되지않던 현상이였습니다.

 

관련 내용을 찾아 보니

 

셀리니엄 HQ에 이런 이슈가 있더군요

 

// 링크

https://github.com/SeleniumHQ/selenium/issues/4292

 

 

 

이슈는 역시 Closed 되어있고

 

셀레니엄팀에서 해줬을 거라고는 생각 하지 않습니다. 

 

(최신버전을 쓰고있는 저에게 되지않으니까요 Stable 버전 기준으로)

 

아래 글 내용들을 읽어 보니

 

결국 우회 처리로 요소를 클릭 하였습니다.

 

그 우회 처리란

  // login 버튼 가져와서 클릭          let loginBtn = await driver.findElement(By.css("input[type='submit']"));         await loginBtn.click(); 

 

기존에는 위와같이 await loginBtn.click() 이 동작하였지만 Windwos 10 에서는 동작하지가 않습니다.

 

그래서 사용하는 우회 방법은

 

Driver 모듈에 있는 executor를 이용하는 방법입니다.

 

일단 JS DOC을 한번 확인해보겠습니다.

 

 

보시면 2개가 있는데 Async 근 비동기방식이고 제가 펼쳐 놓은 이 executor는 동기 방식으로 차이는 없습니다.

 

자 보시면 (Script,... args) 라고 되어있습니다.

 

앞에 파라미터로는 스크립트가 들어갈 것이고 뒤에 arguments로는 WebElement가 들어가야 된다고 쓰여있네요

 

그럼 저 위에 동작하지 않는 login Btn 을 어떻게 클릭을 할것인가 하면

 

말그대로 앞에는 스크립트 ( 클릭 스크립트를 넣을것이고) 뒤에는 가져온 WebElement ( loignBtn 을 넣어서 확인합니다.)

 

 

         await driver.executeScript('return arguments[0].click()",loginBtn);  

단순합니다. 우측에 놓은 element 가 arguments로 넘어오게 되고 1개만 넘겼으니 [0]으로 첫번째 값을 가져오면 됩니다.

 

가져온 후에 click 을 하는 스크립트로 return 을 넣어주면 driver executor에서 해당 반환된 스크립트를 브라우저로

 

인젝션하게 되어 실행을 합니다.

 

해당 이슈에서도 그렇고 여러 Win10 분들은 이와 같은 방안으로 우회를 하고 있는 것으로 보여집니다. 

 

win 10에서 네이버 로그인시 DOM 도 제대로 못가져오는 경우가 있는데

 

이에 대해서는 다음 포스팅때 작성 하도록 하겠습니다.

 

감사합니당

 

 

 

 

앞서 UI 자동화에 대한 기본적인 개요 및 네이버 자동 로그인을 IE 11과 Chrome(66 Stable)로 테스트 해보았습니다.


이제 제대로 업무에 적용하기에 앞서 자동화가 이루어지기 위해서 빠지지 않는 webDriver에 대해 조사해보았습니다.



Selenium WebDriver

Selenium webDriver(셀레니엄 웹드라이버) 는 많은 브라우저(FireFox,Edge,InternetExplorer,Chrome...) , 운영체제 및 프로그래밍언어(ruby,js,C#,JAVA,Python .. ) 등을 지원하며 웹 응용 프로그램들의 테스트 를 단순화 함과 동시에 가속화를 도와주는 툴입니다.

해당 Selenium WebDriver 가나오기전 있었던 Selenium Remote Control(RC) 버전이 있었습니다.

해당 제품의 구동 방식은 이와 같습니다.

[Chrome]


Chrome 바이너리가 실행 되고 Remote Controller를 통해서 해당 Chrome에 메시지를 전달하고 다시 받고 다시 전달 하는 방식을 취득하였으며 지원되는 브라우저도 적었으며 RC구현으로 인해 OS의 제약도 많았습니다. 


또한 해당 메시지 방식을 취득 하다 보니 실제 사용자의 동작으로 보이지 않는 경우도 있었습니다.


이러저러한 문제점으로 인해 Selenium WebDriver(2.0) 이 webDriver + Selenium1.0 으로 나오게 되었으며


Selenium WebDriver 의 장점이 있습니다. 


1. 다채로운 프로그래밍 언어 지원

    기존의 Selenium IDE와는 다르게  Java,PHP,Perl,Ruby,Python,C#,JS 등을 지원합니다.

2. 매우 단순한 사용 법

    기존의 RC사용법은 RC실행 + 드라이버 세팅  + Selenium 세팅 및 스크립트가 따로 가야 하지만

    WebDriver는 StandardAlone으로  내부적으로 한번에 스크립트를 사용하여 제어합니다.

3. 실제 사용자 동작을 예뮬레이션 합니다.

    동작 내용을 확인해 보면 아실수 있겠지만 WebElement 등을 클릭할때 마우스로 인한 포커싱이 되는 모습을 확인 

    이가능합니다.

4. 기존보다 스크립트 실행 속도가 빠릅니다.

    - 해당은 제가 기존을 사용하지 않아서  확인을 못해봤습니다.

5.  API 지원

    WebDriver API 를 통해서 어렵지 않게 개발등이 가능합니다.

 (JS로 하는에 손쉬운거 같진 않지만)


chromedriver.exe가 하는 역할은  selenium에서 webdriver명령을 보낼때 크롬을 제어 하는 방법 을 제공해주는데


이를 크롬 57버전까지는 자동화 확장 기능을 사용하여 크롬과 통신을 원할하게 하였지만


크롬 58 버전 부터는 확장 기능을 제거하고 WebSocket 통신을 사용하고 원격 디버깅을 지원하는 크롬의 dev tool api 에서 모든것을 제어하는 방법으로 바뀌었습니다.


그러므로 자동화 실행시에는 위의 크롬은 현재 자동화 테스트 소프트웨어로 인해 제어되고 있습니다. 라는 메시지가 나타납니다.


설명이 매우 부실해보이지만.. 필요하면 더 추가하겠습니다.


Selenium 관련하여 이슈 트래킹 사이트가 있습니다.


안되 는 것이 있을 경우 이슈트래킹에서 


확인 해보시기 바랍니다.


https://github.com/SeleniumHQ/selenium/issues



또한 최근에(?) 만들어지는 Selenium 개발자가 직접 프로젝트를 합쳐서 만드는 4.0 Alpha 버전에 대한


지원 레밸 정의입니다.


대충 보면

이슈를 재기해도 고치진 않을것이고 fix되지 않은 API 에 대한 보장은 해주지 않겠지만

최선을 다해서 내부적으로 이슈를 찾아서 고칠 것이며  selenium 은 플랫폼 API 와 호환 API 를 릴리즈 한다.

Support Level Definitions

  • supported: A selenium-webdriver release will be API compatible with the platform API, without the use of runtime flags.

  • best effort: Bugs will be investigated as time permits. API compatibility is only guaranteed where required by a supported release. This effectively means the adoption of new JS features, such as ES2015 modules, will depend on what is supported in Node's LTS.

  • unsupported: Bug submissions will be closed as will-not-fix and API compatibility is not guaranteed.






해당 에러는 줌 레벨 문제로 나타나는 에러인데 IE 드라이버로 실행할 때 만 나타나는 현상입니다.


원시적인 방법

보기 -> 확대/축소 - 100% 지정





아래와같이 코드상으로 무시할수 있습니다. IgnorezoomLevel 을 통한 


// 관련 유사 에러 링크 

http://ipex.tistory.com/entry/webUI%EC%9B%B9%EC%9E%90%EB%8F%99%ED%99%94-Error-WebDriver-NodeJS-Instantiate-IE-Driver-with-Capabilities-introduceFlakinessByIgnoringProtectedModeSettingsignoreSettings




첫번째 해결 방안

 

IE를 여신 후 보안 설정을 전부 체크합니다.

 

도구 -> 인터넷 옵션 (alt + T + O)

 

보안 탭 

인터넷 : 하단 보호 모드 사용 체크 

 

로컬인트라넷 : 하단 보호 모드 사용 체크

 

신뢰할 수 있는 사이트 : 하단 보호 모드 사용 체크

 

제한된 사이트 : 하단 보호 모드 사용 체크

 

 

 

 

두번째 해결 방안

 

코드상으로 Ignore하는 방법

 

->

const Capabilities = require('selenium-webdriver/lib/capabilities').Capabilities; let capabilities = Capabilities.ie(); capabilities.set('ignoreProtectedModeSettings', true);  capabilities.set('ignoreZoomSetting', true); 

 

와 같이 옵션을 작성후 빌드시 옵션을 넣어서 무시할수 있도록 

 

해당 방식은 사이드가 있어서 좀더 확인 후 포스팅 하겠습니다.

+ Recent posts