셀레니움 동시 실행 - sellenium dongsi silhaeng

지난 게시글에서 도커를 활용해서 Selenium Hub 컨테이너 및 Selenium Node 컨테이너를 실행하고 테스트 코드에서 이를 연결할 수 있는 방법에 대해 알아보았다. 다만, 구성된 컨테이너를 활용하여 여러 개의 테스트 케이스를 동시에 실행해 보면 병렬 실행되지 않고 큐에 쌓여 있는 것을 확인할 수 있다. (아래 스크린샷 참고)

그 이유는 기본적으로 컨테이너 하나당 하나의 세션만 실행되도록 구성하고 있기 때문인데, Github를 찾아보니 하나의 컨테이너/브라우저에서 하나의 CPU를 사용하면 안정성이 향상되기 때문이라고 한다.

물론 컨테이너 실행 시 환경 변수를 통해 컨테이너 당 세션을 증가시킬 수 있으나 사용 가능한 프로세서보다 많은 브라우저 세션을 실행하는 것을 권장하지 않으며, Selenium Grid를 활용한 영상 녹화 시 동일한 동영상에 여러 브라우저 세션이 녹화될 수 있다고 설명하고 있다. 부작용에 대하여 참고하도록 하자.

셀레니움 동시 실행 - sellenium dongsi silhaeng

Selenium Node 병렬 실행을 위한 환경 변수 추가

환경 변수를 추가하는 방법은 간단하다. 컨테이너 실행 시 SE_NODE_MAX_SESSIONS={늘릴 세션 수량} 옵션을 추가해 주면 된다. AS-IS와 TO-BE를 비교해 보면 다음과 같다.

// AS-IS
$ docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub \
    --shm-size="2g" \
    -e SE_EVENT_BUS_PUBLISH_PORT=4442 \
    -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
    selenium/node-chrome:4.2.1-20220531
// TO-BE 
$ docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub \
    --shm-size="2g" \
    -e SE_EVENT_BUS_PUBLISH_PORT=4442 \
    -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
	-e SE_NODE_MAX_SESSIONS=5 \
    selenium/node-chrome:4.2.1-20220531

그리고 Selenium Hub에 접속해보면 다음과같이 Max. Concurrency가 증가한 것을 확인할 수 있다.

셀레니움 동시 실행 - sellenium dongsi silhaeng

준비된 여러 개의 테스트 케이스를 동시에 실행해서 기존과 비교하여 어떻게 달라졌는지 확인해 보자.

아래 스크린샷을 보면 두 개의 테스트 케이스가 동시에 실행되고 있으며, SE_NODE_MAX_SESSIONS=5 환경 변수를 통해 늘린 5개의 세션 중에 2개가 사용 중인 것을 볼 수 있다. 이로써 UI 자동화 테스트 케이스를 병렬 실행하여 실행 속도를 줄일 수 있고 브라우저 실행 환경(Node)를 보다 쉽고 편리하게 관리할 수 있다.

테스트 자동화 대상은 4개의 디바이스에서 실행되어야 합니다.

  • Android Native App
  • iOS Native App
  • Android Unity App
  • iOS Unity App
  • 동시에 위 4개의 테스트를 동시에 진행하고 싶습니다.

    appium을 이용해 어려대의 디바이스를 동시에 실행해 테스트하기 위해서는 Selenium Grid를 이용합니다.

    Selenium Server가 각 device별로 설정된 4개의 Appium 서버를 연결해 주는 방식입니다.

    Selenium Grid는 Selenium Standalone Server를 이용하며 Selenium 사이트에서 다운받을 수 있습니다.

    현재는 3.7.1버전이네요. selenium-server-standalone-3.7.1.jar 파일을 다운받습니다.

    [http://www.seleniumhq.org/download/]

    jar이기 때문에 java가 설치되어 있어야 합니다.

    다운받은 selenium 서버를 실행시켜 봅시다. terminal로 다운받은 경로로 이동해 아래와 같이 실행시킵니다.

    $ java -jar selenium-server-standalone-3.7.1.jar -role hub

    그리고 브라우저를 열어 아래 rul을 호출해 봅니다.

    http://127.0.0.1:4444/grid/console

    아직 등록된 node가 없어서 아래 화면을 보게 됩니다.

    셀레니움 동시 실행 - sellenium dongsi silhaeng

    이제 4개의 node를 만들어 줍니다.

    각 node에 대한 Configuration을 json파일로 작성해 줍니다.

    grid-native-and-v10.json

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }

    grid-native-ios-6.json

    {
    	"capabilities": [{
    		"deviceName": "iPhone-6",
    		"version": "11.0",
    		"maxInstances": 3,
    		"platform": "iOS",
    		"udid": "73439839ee3db7b59fcdd8bc3aa8cc4862006b7b"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4722/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4722,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4722",
    		"role": "node"
    	}
    }

    grid-unity-and-note5.json

    {
    	"capabilities": [{
    		"deviceName": "Note5",
    		"version": "5.1",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "0715f7ca177b2936"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4723/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4723,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4723",
    		"role": "node"
    	}
    }

    grid-unity-ios-6s.json

    {
    	"capabilities": [{
    		"deviceName": "iPhone-6S",
    		"version": "10.0",
    		"maxInstances": 3,
    		"platform": "iOS",
    		"udid": "1b0fa7ad922b08b79c2adf4f841ec407a8cedd64"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4724/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4724,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4724",
    		"role": "node"
    	}
    }

    android 디바이스의 udid는 adb devices로 확인해 기입합니다.

    $ adb devices
    List of devices attached
    LGF600Kb1134738	device
    0715f7ca177b2936	device

    configuration 정보 중 url, host, post는 Appium Server 정보이며 Port는 4721부터 순차적으로 적어줍니다.

    hub, hubPort, hubHost 정보는 Selenium Server 정보를 기입해 줍니다.

    이제 json파일을 이용해 4개의 appium 서버를 실행합니다. 실행 옵션은 아래와 같습니다.

    Native Android V10

    appium \
    	--nodeconfig grid-native-and-v10.json \
    	--port 4721 \
    	--command-timeout 7200 \
    	--app ./appfiles/KakaoGameSDK_Test_App_3.5.1.148.apk \
    	--device-name V10 \
    	--udid LGF600Kb1134738 \
    	--tmp /Users/tongchunkim/Documents/TestAppium/temp/v10/

    Native iOS 6

    (참고로 저는 iOS 앱을 TestFlight로 관리합니다. 따라서 Appium Server를 실해할 때 --app 옵션을 제외합니다.)

    appium \
    	--nodeconfig grid-native-ios-6.json \
    	--port 4722 \
    	--command-timeout 7200 \
    	--app ./appfiles/com.kakaogames.sdk.sample.ipa \
    	--device-name iPhone-6 \
    	--udid  73439839ee3db7b59fcdd8bc3aa8cc4862006b7b \
    	--tmp /Users/tongchunkim/Documents/TestAppium/temp/iphone6/

    Unity Android Note5

    appium \
    	--nodeconfig grid-unity-and-note5.json \
    	--port 4723 \
    	--command-timeout 7200 \
    	--app ./appfiles/UnityTestAndroid.apk \
    	--device-name Note5 \
    	--udid 0715f7ca177b2936 \
    	--tmp /Users/tongchunkim/Documents/TestAppium/temp/note5/

    Unity iOS 6S

    (참고로 저는 iOS 앱을 TestFlight로 관리합니다. 따라서 Appium Server를 실해할 때 --app 옵션을 제외합니다.)

    appium \
    	--nodeconfig grid-unity-ios-6s.json \
    	--port 4724 \
    	--command-timeout 7200 \
    	--app ./appfiles/com.kakaogames.sdk.unitysample.ipa \
    	--device-name iPhone-6S \
    	--udid  1b0fa7ad922b08b79c2adf4f841ec407a8cedd64 \
    	--tmp /Users/tongchunkim/Documents/TestAppium/temp/iphone6S/

    먼저 Selenium 서버가 실행되어 있는지 확인하고 위 4개의 Appium 서버를 순차적으로 실행합니다.

    그럼 아래와 같이 Selenium 서버에 4개의 Appium 서버가 연결된 것을 확인할 수 있습니다.

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }
    0

    브라우저를 열고 selenium 서버를 호출하면 4개의 node(Appium Server)가 연결된 것을 확인 할 수 있습니다.

    http://127.0.0.1:4444/grid/console

    셀레니움 동시 실행 - sellenium dongsi silhaeng

    이제 Appium Client로 각 Appium Server로 연결해 봅시다.

    각 Client의 SetUp Method들은 아래와 같습니다.

    Native Android V10

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }
    1

    Native iOS 6

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }
    2

    Unity Android Note5

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }
    3

    Unity iOS 6S

    {
    	"capabilities": [{
    		"deviceName": "V10",
    		"version": "6.0",
    		"maxInstances": 3,
    		"platform": "ANDROID",
    		"udid": "LGF600Kb1134738"
    	}],
    	"configuration": {
    		"cleanUpCycle": 2000,
    		"timeout": 30000,
    		"proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
    		"url": "http://127.0.0.1:4721/wd/hub",
    		"host": "127.0.0.1",
    		"port": 4721,
    		"maxSession": 6,
    		"register": true,
    		"registerCycle": 5000,
    		"hub": "http://127.0.0.1:4444/grid/register/",
    		"hubPort": 4444,
    		"hubHost": "127.0.0.1",
    		"remoteHost": "http://127.0.0.1:4721",
    		"role": "node"
    	}
    }
    4

    이제 4개의 Device에서 동시에 테스트할 수 있습니다.