안녕하세요 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');

 

+ Recent posts