Python + pywinauto๋กœ ๋ฐ์Šคํฌํƒ‘ GUI ์ž๋™ํ™”: MS UI ์ž๋™ํ™”์™€ ์นœ๊ตฌ๊ฐ€ ๋˜๋Š” ๋ฐฉ๋ฒ•

ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ”ผ์œˆ์˜คํ†  Windows์—์„œ ๋ฐ์Šคํฌํƒ‘ GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž๋™ํ™”ํ•˜๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ์ง€๋‚œ 2๋…„ ๋™์•ˆ ์ƒˆ๋กœ์šด ์ฃผ์š” ๊ธฐ๋Šฅ์ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.

  • MS UI ์ž๋™ํ™” ๊ธฐ์ˆ ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋™์ผํ•˜๋ฉฐ ์ด์ œ WinForms, WPF, Qt5, Windows Store(UWP) ๋“ฑ Windows์— ์žˆ๋Š” ๊ฑฐ์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐฑ์—”๋“œ/ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ(ํ˜„์žฌ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์Œ: ๊ธฐ๋ณธ๊ฐ’) "win32" ๊ทธ๋ฆฌ๊ณ  ์ƒˆ๋กœ์šด "uia"). ๊ทธ๋Ÿฐ ๋‹ค์Œ ํฌ๋กœ์Šค ํ”Œ๋žซํผ์œผ๋กœ ์›ํ™œํ•˜๊ฒŒ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
  • ๋งˆ์šฐ์Šค ๋ฐ ํ‚ค๋ณด๋“œ์šฉ Win32 ํ›„ํฌ(pyHook ์ •์‹ ์˜ ๋‹จ์ถ•ํ‚ค).

๋˜ํ•œ ๋ฐ์Šคํฌํ†ฑ ์ž๋™ํ™”๋ฅผ ์œ„ํ•ด ์˜คํ”ˆ ์†Œ์Šค์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ๊ฐœ์š”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค(์‹ฌ๊ฐํ•œ ๋น„๊ต๋ฅผ ๊ฐ€์žฅํ•˜์ง€ ์•Š๊ณ ).

์ด ๊ธฐ์‚ฌ๋Š” ๋ฏผ์Šคํฌ์—์„œ ์—ด๋ฆฐ SQA Days 20 ์ปจํผ๋Ÿฐ์Šค ๋ณด๊ณ ์„œ์˜ ์ผ๋ถ€ ์‚ฌ๋ณธ์ž…๋‹ˆ๋‹ค(๋น„๋””์˜ค ๋…นํ™” ะธ ์Šฌ๋ผ์ด๋“œ), ๋ถ€๋ถ„์ ์œผ๋กœ ๋Ÿฌ์‹œ์•„์–ด ๋ฒ„์ „ ์‹œ์ž‘ ์„ค๋ช…์„œ pywinauto์šฉ.

์ด ๋ถ„์•ผ์˜ ์˜คํ”ˆ ์†Œ์Šค์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ๊ฐœ์š”๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฐ์Šคํฌํƒ‘ GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ๊ฒƒ์ด Selenium์ด ์žˆ๋Š” ์›น์˜ ๊ฒฝ์šฐ๋ณด๋‹ค ๋‹ค์†Œ ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ขŒํ‘œ ๋ฐฉ๋ฒ•

ํด๋ฆญ ํฌ์ธํŠธ๋ฅผ ํ•˜๋“œ์ฝ”๋”ฉํ•˜์—ฌ ์„ฑ๊ณต์ ์ธ ํžˆํŠธ๋ฅผ ๊ธฐ์›ํ•ฉ๋‹ˆ๋‹ค.
[+] ํฌ๋กœ์Šค ํ”Œ๋žซํผ์œผ๋กœ ๊ตฌํ˜„์ด ์‰ฝ์Šต๋‹ˆ๋‹ค.
[+] ํ…Œ์ŠคํŠธ ๋…น์Œ์„ "๋…น์Œ-์žฌ์ƒ"ํ•˜๋Š” ๊ฒƒ์€ ์‰ฝ์Šต๋‹ˆ๋‹ค.
[-] ํ™”๋ฉด ํ•ด์ƒ๋„, ํ…Œ๋งˆ, ๊ธ€๊ผด, ์ฐฝ ํฌ๊ธฐ ๋“ฑ์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐ ๊ฐ€์žฅ ๋ถˆ์•ˆ์ •ํ•ฉ๋‹ˆ๋‹ค.
[-] ๋ง‰๋Œ€ํ•œ ์ง€์› ๋…ธ๋ ฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด ๋” ์‰ฌ์šด ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.
[-] ์ž‘์—…๋งŒ ์ž๋™ํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ๊ฒ€์ƒ‰ํ•˜๋Š” ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋„๊ตฌ(ํฌ๋กœ์Šค ํ”Œ๋žซํผ): ์ž๋™ ๋ณต์‚ฌ, PyAutoGUI, PyUserInput ๊ทธ๋ฆฌ๊ณ  ๋งŽ์€ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๋” ๋ณต์žกํ•œ ๋„๊ตฌ์—๋Š” ์ด ๊ธฐ๋Šฅ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค(ํ•ญ์ƒ ํฌ๋กœ์Šค ํ”Œ๋žซํผ์ธ ๊ฒƒ์€ ์•„๋‹˜).

์ขŒํ‘œ ๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅธ ์ ‘๊ทผ๋ฒ•์„ ๋ณด์™„ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์€ ๋งํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž ์ •์˜ ๊ทธ๋ž˜ํ”ฝ์˜ ๊ฒฝ์šฐ ์ƒ๋Œ€ ์ขŒํ‘œ(์ „์ฒด ํ™”๋ฉด์ด ์•„๋‹Œ ์ฐฝ/์š”์†Œ์˜ ์™ผ์ชฝ ์ƒ๋‹จ ๋ชจ์„œ๋ฆฌ์—์„œ)๋ฅผ ํด๋ฆญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํŠนํžˆ ๊ทธ๋ž˜ํ”ฝ์˜ ๊ธธ์ด/ํญ์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒฝ์šฐ ๋งค์šฐ ์•ˆ์ •์ ์ž…๋‹ˆ๋‹ค. ์ „์ฒด ์š”์†Œ(๊ทธ๋Ÿฌ๋ฉด ๋‹ค๋ฅธ ํ™”๋ฉด ํ•ด์ƒ๋„๊ฐ€ ์†์ƒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค).

๋˜ ๋‹ค๋ฅธ ์˜ต์…˜: ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์•ˆ์ •์ ์ธ ์„ค์ •์„ ๊ฐ–์ถ˜ ๋จธ์‹  ํ•˜๋‚˜๋งŒ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค(ํฌ๋กœ์Šค ํ”Œ๋žซํผ์€ ์•„๋‹ˆ์ง€๋งŒ ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ์ข‹์Šต๋‹ˆ๋‹ค).

์ฐธ์กฐ ์ด๋ฏธ์ง€ ์ธ์‹

[+] ํฌ๋กœ์Šค ํ”Œ๋žซํผ
[+-] ์ƒ๋Œ€์ ์œผ๋กœ ์•ˆ์ •์ ์ด์ง€๋งŒ(์ขŒํ‘œ ๋ฐฉ๋ฒ•๋ณด๋‹ค ์ข‹์Œ) ์—ฌ์ „ํžˆ ๋ช‡ ๊ฐ€์ง€ ํŠธ๋ฆญ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
[-+] ์ƒ๋Œ€์ ์œผ๋กœ ๋А๋ฆฝ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์ธ์‹ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์œ„ํ•ด์„œ๋Š” CPU ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
[-] ๋ฌธ์ž ์ธ์‹(OCR)์€ ์›์น™์ ์œผ๋กœ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. => ๋ฌธ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์–ป์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์•„๋Š” ํ•œ, ๊ธฐ์กด OCR ์†”๋ฃจ์…˜์€ ์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ ์ž‘์—…์— ๊ทธ๋‹ค์ง€ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(์•„์ง ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ ์˜๊ฒฌ์„ ๋ณด๋‚ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค).

๋„๊ตฌ : Sikuli, ํ•˜์ธ (Sikuli ํ˜ธํ™˜, ์ˆœ์ˆ˜ Python), PyAutoGUI.

์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ 

[+] ๊ฐ€์žฅ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ์ด๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ํ…์ŠคํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜๋Š” ๋ฐฉ์‹์— ๊ด€๊ณ„์—†์ด ํ…์ŠคํŠธ๋กœ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
[+] ํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค => ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๋” ์‰ฝ๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
[+] ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ€์žฅ ๋น ๋ฆ…๋‹ˆ๋‹ค. CPU ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฑฐ์˜ ์†Œ๋ชจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
[-] ํฌ๋กœ์Šค ํ”Œ๋žซํผ ๋„๊ตฌ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์˜คํ”ˆ ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํ•˜๋‚˜ ๋˜๋Š” ๋‘ ๊ฐœ์˜ ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. Windows/Linux/MacOS๋Š” TestComplete, UFT ๋˜๋Š” Squish์™€ ๊ฐ™์€ ์œ ๋ฃŒ ์ œํ’ˆ์„ ์ œ์™ธํ•˜๊ณ ๋Š” ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
[-] ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์€ ์›์น™์ ์œผ๋กœ ํ•ญ์ƒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด VirtualBox ๋‚ด๋ถ€์˜ ๋กœ๋”ฉ ํ™”๋ฉด์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ์ด๋ฏธ์ง€ ์ธ์‹ ์—†์ด๋Š” ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋งŽ์€ ๊ณ ์ „์ ์ธ ๊ฒฝ์šฐ์—๋Š” ์ ‘๊ทผ์„ฑ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์—ฌ์ „ํžˆ ์ ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” ๋” ์ž์„ธํžˆ ๋…ผ์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋„๊ตฌ : ํ…Œ์ŠคํŠธ์Šคํƒ.ํ™”์ดํŠธ C#์—์„œ๋Š” ์œ„๋‹ˆ์—„.๋ฐ์Šคํฌํƒ‘ C#(์…€๋ ˆ๋Š„ ํ˜ธํ™˜) MS ์œˆ์•ฑ ๋“œ๋ผ์ด๋ฒ„ C#(Appium ํ˜ธํ™˜), ํ”ผ์œˆ์˜คํ† , ํ”ผ์•„ํ†ฐ (LDTP ํ˜ธํ™˜) Python-UI์ž๋™ํ™”-Windows์šฉ, R์ž๋™ํ™” ๋ฃจ๋น„์—์„œ๋Š” LDTP (Linux Desktop Testing Project) ๋ฐ ํ•ด๋‹น Windows ๋ฒ„์ „ ์ฝ”๋ธŒ๋ผ.

LDTP๋Š” ์•„๋งˆ๋„ ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์œ ์ผํ•œ ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์˜คํ”ˆ ์†Œ์Šค ๋„๊ตฌ(๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œํ’ˆ๊ตฐ)์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๋‹ค์ง€ ์ธ๊ธฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ง์ ‘ ์‚ฌ์šฉํ•ด๋ณธ ์ ์€ ์—†์ง€๋งŒ ๋ฆฌ๋ทฐ์— ๋”ฐ๋ฅด๋ฉด ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๊ฐ€์žฅ ํŽธ๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธ์ •์ ์ธ ํ”ผ๋“œ๋ฐฑ์ด ์žˆ์œผ์‹œ๋ฉด ๋Œ“๊ธ€๋กœ ๊ณต์œ ํ•ด์ฃผ์„ธ์š”.

๋ฐฑ๋„์–ด ํ…Œ์ŠคํŠธ(์‹ค๋‚ด ์ž์ „๊ฑฐ๋ผ๊ณ ๋„ ํ•จ)

ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ๊ฐœ๋ฐœ์ž๋Š” ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด ๋‚ด๋ถ€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„œ๋น„์Šค TCP ์„œ๋ฒ„๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ์„ ํ…Œ์ŠคํŠธํ•˜๊ณ , ํด๋ฆญํ•  ํ•ญ๋ชฉ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์œ„์น˜ ๋“ฑ ํ…์ŠคํŠธ ๋ช…๋ น์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณดํŽธ์ ์ด์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ๋ฐ์Šคํฌํƒ‘ ์ ‘๊ทผ์„ฑ ๊ธฐ์ˆ 

์ข‹์€ ์˜ค๋ž˜๋œ Win32 API

WPF ์ถœ์‹œ ์ด์ „์— ์ž‘์„ฑ๋œ ๋Œ€๋ถ€๋ถ„์˜ Windows ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ Windows ์Šคํ† ์–ด๋Š” ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ๋“  Win32 API๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, MFC, WTL, C++ Builder, Delphi, VB6 ๋“ฑ ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๋„๊ตฌ๋Š” Win32 API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Windows Forms๋„ ๋Œ€๋ถ€๋ถ„ Win32 API์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค.

๋„๊ตฌ : AutoIt์„ (VB์™€ ์œ ์‚ฌ) ๋ฐ Python ๋ž˜ํผ ํ”ผ์˜คํ† ์ž‡, AutoHotkey๋ฅผ (์ž์‹ ์˜ ์–ธ์–ด์—๋Š” IDispatch COM ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค), ํ”ผ์œˆ์˜คํ†  (ํŒŒ์ด์ฌ) R์ž๋™ํ™” (๋ฃจ๋น„) win32-autogui (๋ฃจ๋น„).

๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ UI ์ž๋™ํ™”

์ฃผ์š” ์ด์ : MS UI ์ž๋™ํ™” ๊ธฐ์ˆ ์€ ๋“œ๋ฌธ ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๊ณ  Windows์—์„œ ๋Œ€๋ถ€๋ถ„์˜ GUI ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์ œ: Win32 API๋ณด๋‹ค ๋ฐฐ์šฐ๊ธฐ๊ฐ€ ๊ทธ๋ฆฌ ์‰ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์•„๋ฌด๋„ ๊ทธ ์œ„์— ํฌ์žฅ์ง€๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์ด๊ฒƒ์€ ์‚ฌ์šฉ์ž ์ •์˜ COM ์ธํ„ฐํŽ˜์ด์Šค ์„ธํŠธ์ž…๋‹ˆ๋‹ค(์ฃผ๋กœ UIAutomationCore.dll), ๋‹ค์Œ ํ˜•์‹์˜ .NET ๋ž˜ํผ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. namespace System.Windows.Automation. ๊ทธ๋Ÿฐ๋ฐ ์ผ๋ถ€ UI ์š”์†Œ๊ฐ€ ๋ˆ„๋ฝ๋  ์ˆ˜ ์žˆ๋Š” ๋ฒ„๊ทธ๊ฐ€ ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ UIAutomationCore.dll์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค(C#์˜ UiaComWrapper์— ๋Œ€ํ•ด ๋“ค์–ด๋ณธ ์ ์ด ์žˆ๋‹ค๋ฉด ์ด๊ฒƒ์ด ๋ฐ”๋กœ ๊ทธ๊ฒƒ์ž…๋‹ˆ๋‹ค).

COM ์ธํ„ฐํŽ˜์ด์Šค ์œ ํ˜•:

(1) ๊ธฐ๋ณธ IUknown โ€“ โ€œ๋ชจ๋“  ์•…์˜ ๊ทผ์›โ€. ๊ฐ€์žฅ ๋‚ฎ์€ ์ˆ˜์ค€์ด๋ฉฐ ๊ฒฐ์ฝ” ์‚ฌ์šฉ์ž ์นœํ™”์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
(2) IDispatch ๋ฐ ํŒŒ์ƒ๋ฌผ(์˜ˆ: Excel.Application), win32com.client ํŒจํ‚ค์ง€(pyWin32์— ํฌํ•จ๋จ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Python์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ํŽธ๋ฆฌํ•˜๊ณ  ์•„๋ฆ„๋‹ค์šด ์˜ต์…˜์ž…๋‹ˆ๋‹ค.
(3) ํƒ€์‚ฌ Python ํŒจํ‚ค์ง€์™€ ํ•จ๊ป˜ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์ธํ„ฐํŽ˜์ด์Šค ์ปดํƒ€์ž….

๋„๊ตฌ : ํ…Œ์ŠคํŠธ์Šคํƒ.ํ™”์ดํŠธ C#์—์„œ๋Š” ํ”ผ์œˆ์˜คํ†  0.6.0 +, ์œ„๋‹ˆ์—„.๋ฐ์Šคํฌํƒ‘ C#์—์„œ๋Š” Python-UI์ž๋™ํ™”-Windows์šฉ (UIAutomationCore.dll์— ๋Œ€ํ•œ C ๋ž˜ํผ์˜ ์†Œ์Šค ์ฝ”๋“œ๋Š” ๊ณต๊ฐœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.) R์ž๋™ํ™” ๋ฃจ๋น„์—์„œ.

AT-SPI

Linux ์ œํ’ˆ๊ตฐ์˜ ๊ฑฐ์˜ ๋ชจ๋“  ์ถ•์ด X Window ์‹œ์Šคํ…œ(Fedora 25์—์„œ๋Š” "X"๊ฐ€ Wayland๋กœ ๋ณ€๊ฒฝ๋จ)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋˜์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  "X"๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์ƒ์œ„ ์ฐฝ๊ณผ ๋งˆ์šฐ์Šค/ ๊ฑด๋ฐ˜. ๋ฒ„ํŠผ, ๋ชฉ๋ก ์ƒ์ž ๋“ฑ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋ถ„์„์„ ์œ„ํ•ด AT-SPI ๊ธฐ์ˆ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์ฐฝ ๊ด€๋ฆฌ์ž์—๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ์ž๋™ํ™”๋œ GUI๋ฅผ ์ œ๊ณตํ•˜๋Š” ์†Œ์œ„ AT-SPI ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋ฐ๋ชฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค(์ ์–ด๋„ Qt ๋ฐ GTK๊ฐ€ ์ง€์›๋จ).

๋„๊ตฌ : pyatspi2.

์ œ ์ƒ๊ฐ์—๋Š” pyatspi2์—๋Š” PyGObject์™€ ๊ฐ™์€ ์ข…์†์„ฑ์ด ๋„ˆ๋ฌด ๋งŽ์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์ˆ  ์ž์ฒด๋Š” ์ผ๋ฐ˜ ๋™์  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ libatspi.so. ์ด์žˆ๋‹ค ์ฐธ์กฐ ์„ค๋ช…์„œ. pywinauto ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฒฝ์šฐ libatspi.so ๋ฐ ctypes ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ AT-SPI ์ง€์›์„ ๊ตฌํ˜„ํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค. GTK+์™€ Qt ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ์•ฝ๊ฐ„ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์—๋งŒ ์ž‘์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Linux๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•˜๋Š” pywinauto 0.7.0์ด 2018๋…„ ์ƒ๋ฐ˜๊ธฐ์— ์ถœ์‹œ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.

Apple ์ ‘๊ทผ์„ฑ API

MacOS์—๋Š” ์ž์ฒด ์ž๋™ํ™” ์–ธ์–ด์ธ AppleScript๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  Python์—์„œ ์ด์™€ ๊ฐ™์€ ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ObjectiveC์˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. MacOS 10.6๋ถ€ํ„ฐ pyobjc ํŒจํ‚ค์ง€๊ฐ€ ์‚ฌ์ „ ์„ค์น˜๋œ Python์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋˜ํ•œ pywinauto์˜ ํ–ฅํ›„ ์ง€์›์„ ์œ„ํ•œ ์ข…์†์„ฑ์„ ๋‚˜์—ดํ•˜๋Š” ๊ฒƒ์„ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋„๊ตฌ: Apple ์Šคํฌ๋ฆฝํŠธ ์–ธ์–ด ์™ธ์—๋„ ์ฃผ๋ชฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„ํ† ๋งฅ, ์ผ๋ช… pyatom. LDTP์™€ ์ธํ„ฐํŽ˜์ด์Šค ํ˜ธํ™˜๋˜์ง€๋งŒ ๋…๋ฆฝํ˜• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๊ฐ€์ง€๊ณ ์žˆ๋‹ค macOS์˜ iTunes ์ž๋™ํ™” ์˜ˆ, ๋‚ด ํ•™์ƒ์ด ์“ด ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•Œ๋ ค์ง„ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค: ์œ ์—ฐํ•œ ํƒ€์ด๋ฐ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(๋ฉ”์„œ๋“œ waitFor*). ํ•˜์ง€๋งŒ ์ „๋ฐ˜์ ์œผ๋กœ ๋‚˜์œ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค.

pywinauto๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

์ฒซ ๋ฒˆ์งธ ๋‹จ๊ณ„๋Š” GUI ๊ฐœ์ฒด ๊ฒ€์‚ฌ๊ธฐ(์ŠคํŒŒ์ด ๋„๊ตฌ๋ผ๊ณ  ํ•จ)๋กœ ๋ฌด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์š”์†Œ์˜ ๊ณ„์ธต ๊ตฌ์กฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€, ์–ด๋–ค ์†์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋“ฑ ๋‚ด๋ถ€์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์—ฐ๊ตฌํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ํ˜„์žฅ ์กฐ์‚ฌ๊ด€:

  • ์ŠคํŒŒ์ด++ - Express ๋˜๋Š” Community Edition์„ ํฌํ•จํ•œ Visual Studio์— ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Win32 API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ทธ์˜ ํด๋ก ๋„ ์•Œ๋ ค์ ธ ์žˆ์Šต๋‹ˆ๋‹ค AutoIt ์ฐฝ ์ •๋ณด.
  • ๊ฒ€์‚ฌ.exe - Windows SDK์— ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์„ค์น˜ํ–ˆ๋‹ค๋ฉด 64๋น„ํŠธ Windows์—์„œ๋Š” ํด๋”์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. C:Program Files (x86)Windows Kits<winver>binx64. ์ธ์ŠคํŽ™ํ„ฐ ์ž์ฒด์—์„œ ๋ชจ๋“œ๋ฅผ ์„ ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค UI ์ž๋™ํ™” MS AA(UI ์ž๋™ํ™”์˜ ์กฐ์ƒ์ธ Active Accessibility) ๋Œ€์‹ .

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ฒ ์ €ํ•˜๊ฒŒ ๊ฒ€ํ† ํ•œ ํ›„ ์‚ฌ์šฉํ•  ๋ฐฑ์—”๋“œ๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. Application ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ๋ฐฑ์—”๋“œ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฐฑ์—”๋“œ=โ€win32โ€ณ โ€” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜์ง€๋งŒ MFC, WTL, VB6 ๋ฐ ๊ธฐํƒ€ ๋ ˆ๊ฑฐ์‹œ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ๊ณผ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐฑ์—”๋“œ=โ€uiaโ€ โ€” MS UI ์ž๋™ํ™”๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด ๋ฐฑ์—”๋“œ: WPF ๋ฐ WinForms์™€ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. Delphi ๋ฐ Windows Store ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋„ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. Qt5 ๋ฐ ์ผ๋ถ€ Java ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ Inspect.exe๊ฐ€ ์š”์†Œ์™€ ํ•ด๋‹น ์†์„ฑ์„ ํ™•์ธํ•˜๋Š” ๊ฒฝ์šฐ ์ด ๋ฐฑ์—”๋“œ๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ์›์น™์ ์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €๋Š” UI ์ž๋™ํ™”๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค(Mozilla๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Chrome์„ ์‹œ์ž‘ํ•  ๋•Œ ๋ช…๋ น์ค„ ํ‚ค๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค). --force-renderer-accessibilityInspect.exe์˜ ํŽ˜์ด์ง€ ์š”์†Œ๋ฅผ ๋ณด๋ ค๋ฉด). ๋ฌผ๋ก  ์ด ๋ถ„์•ผ์—์„œ ์…€๋ ˆ๋Š„๊ณผ์˜ ๊ฒฝ์Ÿ์€ ๊ฑฐ์˜ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋กœ ์ž‘์—…ํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค(์ œํ’ˆ ๊ฐ„ ์‹œ๋‚˜๋ฆฌ์˜ค์— ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Œ).

์ž๋™ํ™”๋ฅผ ์œ„ํ•œ ์ง„์ž…์ 

์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ๊ด‘๋ฒ”์œ„ํ•˜๊ฒŒ ์—ฐ๊ตฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ Application ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ธ ๊ฐ์ฒด์— ์—ฐ๊ฒฐํ•  ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋‹จ์ง€ ํ‘œ์ค€ ํด๋ž˜์Šค์˜ ๋ณต์ œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. subprocess.Popen, ์ฆ‰ ๋ชจ๋“  ์ž‘์—…์„ ํ”„๋กœ์„ธ์Šค ๊ฒฝ๊ณ„๋กœ ์ œํ•œํ•˜๋Š” ์ž…๋ ฅ ๊ฐœ์ฒด์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ์‹คํ–‰ ์ค‘์ด์ง€๋งŒ ๋‚˜๋จธ์ง€ ์ธ์Šคํ„ด์Šค๋Š” ๊ฑด๋“œ๋ฆฌ๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')

# ะžะฟะธัˆะตะผ ะพะบะฝะพ, ะบะพั‚ะพั€ะพะต ั…ะพั‚ะธะผ ะฝะฐะนั‚ะธ ะฒ ะฟั€ะพั†ะตััะต Notepad.exe
dlg_spec = app.UntitledNotepad
# ะถะดะตะผ ะฟะพะบะฐ ะพะบะฝะพ ั€ะตะฐะปัŒะฝะพ ะฟะพัะฒะธั‚ัั
actionable_dlg = dlg_spec.wait('visible')

์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋™์‹œ์— ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ์ˆ˜์—…์ด ๋„์›€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Desktop. ์˜ˆ๋ฅผ ๋“ค์–ด, Win10์˜ ๊ณ„์‚ฐ๊ธฐ์—์„œ ์š”์†Œ์˜ ๊ณ„์ธต ๊ตฌ์กฐ๋Š” ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค์— ๋ถ„์‚ฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค(๋ฟ๋งŒ ์•„๋‹ˆ๋ผ calc.exe). ๊ทธ๋ž˜์„œ ๋ณ„๊ฑฐ ์—†์Œ Desktop ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

from subprocess import Popen
from pywinauto import Desktop

Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')

๋ฃจํŠธ ๊ฐ์ฒด(Application ๋˜๋Š” Desktop)์€ ๋ฐฑ์—”๋“œ๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ์œ ์ผํ•œ ์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ชจ๋“  ๊ฒƒ์€ ๋‚˜์ค‘์— ๋…ผ์˜๋  "์‚ฌ์–‘->๋ž˜ํผ" ๊ฐœ๋…์— ํˆฌ๋ช…ํ•˜๊ฒŒ ์†ํ•ฉ๋‹ˆ๋‹ค.

์ฐฝ/์š”์†Œ ์‚ฌ์–‘

์ด๊ฒƒ์ด pywinauto ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๊ตฌ์ถ•๋œ ํ•ต์‹ฌ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. ์ฐฝ/์š”์†Œ๊ฐ€ ์•„์ง ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ด๋ฏธ ๋‹ซํ˜€ ์žˆ๋”๋ผ๋„ ๋Œ€๋žต์ ์œผ๋กœ ๋˜๋Š” ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐฝ ์‚ฌ์–‘(๊ฐ์ฒด ์ฐฝ์‚ฌ์–‘)์€ ์‹ค์ œ ์ฐฝ์ด๋‚˜ ์š”์†Œ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ธฐ์ค€์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

์ƒ์„ธํ•œ ์ฐฝ ์‚ฌ์–‘์˜ ์˜ˆ:

>>> dlg_spec = app.window(title='Untitled - Notepad')

>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>

>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>

์ฐฝ ๊ฒ€์ƒ‰ ์ž์ฒด๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. .wrapper_object(). ์‹ค์ œ ์ฐฝ/์š”์†Œ์— ๋Œ€ํ•œ ํŠน์ • "๋ž˜ํผ"๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ElementNotFoundError (๋•Œ๋•Œ๋กœ ElementAmbiguousError, ์—ฌ๋Ÿฌ ์š”์†Œ๊ฐ€ ๋ฐœ๊ฒฌ๋œ ๊ฒฝ์šฐ, ์ฆ‰ ๊ฒ€์ƒ‰ ๊ธฐ์ค€์„ ๋ช…ํ™•ํžˆ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด "๋ž˜ํผ"๋Š” ์š”์†Œ๋กœ ์ผ๋ถ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜ ํ•ด๋‹น ์š”์†Œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Python์€ ํ˜ธ์ถœ์„ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค .wrapper_object()์ด๋ฏ€๋กœ ์ตœ์ข… ์ฝ”๋“œ๊ฐ€ ๋” ์งง์•„์ง‘๋‹ˆ๋‹ค. ๋””๋ฒ„๊น… ๋ชฉ์ ์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๋‘ ์ค„์€ ์ •ํ™•ํžˆ ๋™์ผํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

dlg_spec.wrapper_object().minimize() # debugging
dlg_spec.minimize() # production

์ฐฝ ์‚ฌ์–‘์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ธฐ์ค€์€ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ž…๋‹ˆ๋‹ค.

# ะผะพะณัƒั‚ ะธะผะตั‚ัŒ ะฝะตัะบะพะปัŒะบะพ ัƒั€ะพะฒะฝะตะน
app.window(title_re='.* - Notepad$').window(class_name='Edit')

# ะผะพะถะฝะพ ะบะพะผะฑะธะฝะธั€ะพะฒะฐั‚ัŒ ะบั€ะธั‚ะตั€ะธะธ (ะบะฐะบ AND) ะธ ะฝะต ะพะณั€ะฐะฝะธั‡ะธะฒะฐั‚ัŒัั ะพะดะฝะธะผ ะฟั€ะพั†ะตััะพะผ ะฟั€ะธะปะพะถะตะฝะธั
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')

๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ธฐ์ค€ ๋ชฉ๋ก์€ ํ•จ์ˆ˜ ๋ฌธ์„œ์— ์žˆ์Šต๋‹ˆ๋‹ค. pywinauto.findwindows.find_elements(โ€ฆ).

์†์„ฑ๊ณผ ํ‚ค๋ฅผ ํ†ตํ•œ ์ ‘๊ทผ์˜ ๋งˆ๋ฒ•

Python์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฐฝ ์‚ฌ์–‘์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค๊ณ  ๊ฐ์ฒด ์†์„ฑ์„ ๋™์ ์œผ๋กœ ์ธ์‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋ฉ”์†Œ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์žฌ์ •์˜๋ฉ๋‹ˆ๋‹ค.) __getattribute__). ๋ฌผ๋ก  ์†์„ฑ ์ด๋ฆ„์—๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„๊ณผ ๋™์ผํ•œ ์ œํ•œ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค(๊ณต๋ฐฑ, ์‰ผํ‘œ ๋˜๋Š” ๊ธฐํƒ€ ํŠน์ˆ˜ ๋ฌธ์ž๋ฅผ ์‚ฝ์ž…ํ•  ์ˆ˜ ์—†์Œ). ๋‹คํ–‰์Šค๋Ÿฝ๊ฒŒ๋„ pywinauto๋Š” ์˜คํƒ€์™€ ์ž‘์€ ๋ณ€ํ˜•์— ๊ฐ•ํ•œ ์†Œ์œ„ "์ตœ์  ์ผ์น˜" ๊ฒ€์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

app.UntitledNotepad
# ั‚ะพ ะถะต ัะฐะผะพะต, ั‡ั‚ะพ
app.window(best_match='UntitledNotepad')

์—ฌ์ „ํžˆ ์œ ๋‹ˆ์ฝ”๋“œ ๋ฌธ์ž์—ด(์˜ˆ: ๋Ÿฌ์‹œ์•„์–ด์˜ ๊ฒฝ์šฐ), ๊ณต๋ฐฑ ๋“ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ผ๋ฐ˜ ์‚ฌ์ „์ธ ๊ฒƒ์ฒ˜๋Ÿผ ํ‚ค๋กœ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

app['Untitled - Notepad']
# ั‚ะพ ะถะต ัะฐะผะพะต, ั‡ั‚ะพ
app.window(best_match='Untitled - Notepad')

๋งˆ๋ฒ•์˜ ์ด๋ฆ„์— ๋Œ€ํ•œ ๋‹ค์„ฏ ๊ฐ€์ง€ ๊ทœ์น™

ํ‘œ์ค€ ๋งˆ๋ฒ• ์ด๋ฆ„์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๊ฒ€์ƒ‰ ์ „ ์š”์†Œ์— ํ• ๋‹น๋œ ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค. ํ‘œ์ค€๊ณผ ์ถฉ๋ถ„ํžˆ ์œ ์‚ฌํ•œ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋ฉด ํ•ด๋‹น ์š”์†Œ๊ฐ€ ๊ฒ€์ƒ‰๋ฉ๋‹ˆ๋‹ค.

  1. ์ œ๋ชฉ(ํ…์ŠคํŠธ, ์ด๋ฆ„)๋ณ„: app.Properties.OK.click()
  2. ํ…์ŠคํŠธ ๋ฐ ์š”์†Œ ์œ ํ˜•๋ณ„: app.Properties.OKButton.click()
  3. ์œ ํ˜• ๋ฐ ๊ฐœ์ˆ˜๋ณ„: app.Properties.Button3.click() (์ด๋ฆ„ Button0 ะธ Button1 ๋ฐœ๊ฒฌ๋œ ์ฒซ ๋ฒˆ์งธ ์š”์†Œ์— ๋ฐ”์ธ๋”ฉ๋ฉ๋‹ˆ๋‹ค. Button2 - ๋‘ ๋ฒˆ์งธ๊นŒ์ง€, ๊ทธ๋ฆฌ๊ณ  ์ˆœ์„œ๋Œ€๋กœ - ์ด๊ฒƒ์ด ์—ญ์‚ฌ์ ์œผ๋กœ ์ผ์–ด๋‚œ ์ผ์ž…๋‹ˆ๋‹ค)
  4. ์ •์  ํ…์ŠคํŠธ(์™ผ์ชฝ ๋˜๋Š” ์œ„์ชฝ) ๋ฐ ์œ ํ˜•๋ณ„: app.OpenDialog.FileNameEdit.set_text("") (๋™์  ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋Š” ์š”์†Œ์— ์œ ์šฉํ•จ)
  5. ์œ ํ˜•๋ณ„ ๋ฐ ๋‚ด๋ถ€ ํ…์ŠคํŠธ๋ณ„: app.Properties.TabControlSharing.select("General")

์ผ๋ฐ˜์ ์œผ๋กœ ๋‘ ๊ฐœ ๋˜๋Š” ์„ธ ๊ฐœ์˜ ๊ทœ์น™์ด ๋™์‹œ์— ์ ์šฉ๋˜๋ฉฐ ๋“œ๋ฌผ๊ฒŒ ๋” ๋งŽ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ์š”์†Œ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŠน์ • ์ด๋ฆ„์„ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. print_control_identifiers(). ํ™”๋ฉด๊ณผ ํŒŒ์ผ ๋ชจ๋‘์— ์š”์†Œ ํŠธ๋ฆฌ๋ฅผ ์ธ์‡„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์š”์†Œ์— ๋Œ€ํ•ด ํ‘œ์ค€ ๋งˆ๋ฒ• ์ด๋ฆ„์ด ์ธ์‡„๋ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ํ•˜์œ„ ์š”์†Œ์— ๋Œ€ํ•œ ๋ณด๋‹ค ์ž์„ธํ•œ ์‚ฌ์–‘์„ ๋ณต์‚ฌํ•˜์—ฌ ๋ถ™์—ฌ๋„ฃ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ์˜ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

app.Properties.child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")

์š”์†Œ ํŠธ๋ฆฌ ์ž์ฒด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋‹ค์†Œ ํฐ ์กฑ๋ณด์ž…๋‹ˆ๋‹ค.

>>> app.Properties.print_control_identifiers()

Control Identifiers:

Dialog - 'Windows NT Properties'    (L688, T518, R1065, B1006)
[u'Windows NT PropertiesDialog', u'Dialog', u'Windows NT Properties']
child_window(data-gt-translate-attributes='["title"]' title="Windows NT Properties", control_type="Window")
   |
   | Image - ''    (L717, T589, R749, B622)
   | [u'', u'0', u'Image1', u'Image0', 'Image', u'1']
   | child_window(auto_id="13057", control_type="Image")
   |
   | Image - ''    (L717, T630, R1035, B632)
   | ['Image2', u'2']
   | child_window(auto_id="13095", control_type="Image")
   |
   | Edit - 'Folder name:'    (L790, T596, R1036, B619)
   | [u'3', 'Edit', u'Edit1', u'Edit0']
   | child_window(data-gt-translate-attributes='["title"]' title="Folder name:", auto_id="13156", control_type="Edit")
   |
   | Static - 'Type:'    (L717, T643, R780, B658)
   | [u'Type:Static', u'Static', u'Static1', u'Static0', u'Type:']
   | child_window(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13080", control_type="Text")
   |
   | Edit - 'Type:'    (L790, T643, R1036, B666)
   | [u'4', 'Edit2', u'Type:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Type:", auto_id="13059", control_type="Edit")
   |
   | Static - 'Location:'    (L717, T669, R780, B684)
   | [u'Location:Static', u'Location:', u'Static2']
   | child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13089", control_type="Text")
   |
   | Edit - 'Location:'    (L790, T669, R1036, B692)
   | ['Edit3', u'Location:Edit', u'5']
   | child_window(data-gt-translate-attributes='["title"]' title="Location:", auto_id="13065", control_type="Edit")
   |
   | Static - 'Size:'    (L717, T695, R780, B710)
   | [u'Size:Static', u'Size:', u'Static3']
   | child_window(data-gt-translate-attributes='["title"]' title="Size:", auto_id="13081", control_type="Text")
   |
   | Edit - 'Size:'    (L790, T695, R1036, B718)
   | ['Edit4', u'6', u'Size:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Size:", auto_id="13064", control_type="Edit")
   |
   | Static - 'Size on disk:'    (L717, T721, R780, B736)
   | [u'Size on disk:', u'Size on disk:Static', u'Static4']
   | child_window(data-gt-translate-attributes='["title"]' title="Size on disk:", auto_id="13107", control_type="Text")
   |
   | Edit - 'Size on disk:'    (L790, T721, R1036, B744)
   | ['Edit5', u'7', u'Size on disk:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Size on disk:", auto_id="13106", control_type="Edit")
   |
   | Static - 'Contains:'    (L717, T747, R780, B762)
   | [u'Contains:1', u'Contains:0', u'Contains:Static', u'Static5', u'Contains:']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13088", control_type="Text")
   |
   | Edit - 'Contains:'    (L790, T747, R1036, B770)
   | [u'8', 'Edit6', u'Contains:Edit']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13087", control_type="Edit")
   |
   | Image - 'Contains:'    (L717, T773, R1035, B775)
   | [u'Contains:Image', 'Image3', u'Contains:2']
   | child_window(data-gt-translate-attributes='["title"]' title="Contains:", auto_id="13096", control_type="Image")
   |
   | Static - 'Created:'    (L717, T786, R780, B801)
   | [u'Created:', u'Created:Static', u'Static6', u'Created:1', u'Created:0']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13092", control_type="Text")
   |
   | Edit - 'Created:'    (L790, T786, R1036, B809)
   | [u'Created:Edit', 'Edit7', u'9']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13072", control_type="Edit")
   |
   | Image - 'Created:'    (L717, T812, R1035, B814)
   | [u'Created:Image', 'Image4', u'Created:2']
   | child_window(data-gt-translate-attributes='["title"]' title="Created:", auto_id="13097", control_type="Image")
   |
   | Static - 'Attributes:'    (L717, T825, R780, B840)
   | [u'Attributes:Static', u'Static7', u'Attributes:']
   | child_window(data-gt-translate-attributes='["title"]' title="Attributes:", auto_id="13091", control_type="Text")
   |
   | CheckBox - 'Read-only (Only applies to files in folder)'    (L790, T825, R1035, B841)
   | [u'CheckBox0', u'CheckBox1', 'CheckBox', u'Read-only (Only applies to files in folder)CheckBox', u'Read-only (Only applies to files in folder)']
   | child_window(data-gt-translate-attributes='["title"]' title="Read-only (Only applies to files in folder)", auto_id="13075", control_type="CheckBox")
   |
   | CheckBox - 'Hidden'    (L790, T848, R865, B864)
   | ['CheckBox2', u'HiddenCheckBox', u'Hidden']
   | child_window(data-gt-translate-attributes='["title"]' title="Hidden", auto_id="13076", control_type="CheckBox")
   |
   | Button - 'Advanced...'    (L930, T845, R1035, B868)
   | [u'Advanced...', u'Advanced...Button', 'Button', u'Button1', u'Button0']
   | child_window(data-gt-translate-attributes='["title"]' title="Advanced...", auto_id="13154", control_type="Button")
   |
   | Button - 'OK'    (L814, T968, R889, B991)
   | ['Button2', u'OK', u'OKButton']
   | child_window(data-gt-translate-attributes='["title"]' title="OK", auto_id="1", control_type="Button")
   |
   | Button - 'Cancel'    (L895, T968, R970, B991)
   | ['Button3', u'CancelButton', u'Cancel']
   | child_window(data-gt-translate-attributes='["title"]' title="Cancel", auto_id="2", control_type="Button")
   |
   | Button - 'Apply'    (L976, T968, R1051, B991)
   | ['Button4', u'ApplyButton', u'Apply']
   | child_window(data-gt-translate-attributes='["title"]' title="Apply", auto_id="12321", control_type="Button")
   |
   | TabControl - ''    (L702, T556, R1051, B962)
   | [u'10', u'TabControlSharing', u'TabControlPrevious Versions', u'TabControlSecurity', u'TabControl', u'TabControlCustomize']
   | child_window(auto_id="12320", control_type="Tab")
   |    |
   |    | TabItem - 'General'    (L704, T558, R753, B576)
   |    | [u'GeneralTabItem', 'TabItem', u'General', u'TabItem0', u'TabItem1']
   |    | child_window(data-gt-translate-attributes='["title"]' title="General", control_type="TabItem")
   |    |
   |    | TabItem - 'Sharing'    (L753, T558, R801, B576)
   |    | [u'Sharing', u'SharingTabItem', 'TabItem2']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Sharing", control_type="TabItem")
   |    |
   |    | TabItem - 'Security'    (L801, T558, R851, B576)
   |    | [u'Security', 'TabItem3', u'SecurityTabItem']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Security", control_type="TabItem")
   |    |
   |    | TabItem - 'Previous Versions'    (L851, T558, R947, B576)
   |    | [u'Previous VersionsTabItem', u'Previous Versions', 'TabItem4']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Previous Versions", control_type="TabItem")
   |    |
   |    | TabItem - 'Customize'    (L947, T558, R1007, B576)
   |    | [u'CustomizeTabItem', 'TabItem5', u'Customize']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Customize", control_type="TabItem")
   |
   | TitleBar - 'None'    (L712, T521, R1057, B549)
   | ['TitleBar', u'11']
   |    |
   |    | Menu - 'System'    (L696, T526, R718, B548)
   |    | [u'System0', u'System', u'System1', u'Menu', u'SystemMenu']
   |    | child_window(data-gt-translate-attributes='["title"]' title="System", auto_id="MenuBar", control_type="MenuBar")
   |    |    |
   |    |    | MenuItem - 'System'    (L696, T526, R718, B548)
   |    |    | [u'System2', u'MenuItem', u'SystemMenuItem']
   |    |    | child_window(data-gt-translate-attributes='["title"]' title="System", control_type="MenuItem")
   |    |
   |    | Button - 'Close'    (L1024, T519, R1058, B549)
   |    | [u'CloseButton', u'Close', 'Button5']
   |    | child_window(data-gt-translate-attributes='["title"]' title="Close", control_type="Button")

์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ์ „์ฒด ํŠธ๋ฆฌ๋ฅผ ์ธ์‡„ํ•˜๋Š” ๊ฒƒ์ด ๋А๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ(์˜ˆ๋ฅผ ๋“ค์–ด iTunes์—์„œ๋Š” ํ•˜๋‚˜์˜ ํƒญ์— ์ตœ๋Œ€ 3์ฒœ ๊ฐœ์˜ ์š”์†Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค!) ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. depth (๊นŠ์ด): depth=1 - ์š”์†Œ ์ž์ฒด, depth=2 - ์ง๊ณ„ ์ž๋…€๋งŒ ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ ์‹œ ์‚ฌ์–‘์—์„œ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. child_window.

์˜ˆ

์ง€์†์ ์œผ๋กœ ๋ณด์ถฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค ์ €์žฅ์†Œ์˜ ์˜ˆ์ œ ๋ชฉ๋ก. ์ตœ๊ทผ์—๋Š” WireShark ๋„คํŠธ์›Œํฌ ๋ถ„์„๊ธฐ์˜ ์ž๋™ํ™”์— ์ฃผ๋ชฉํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(์ด๊ฒƒ์€ Qt5 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ข‹์€ ์˜ˆ์ž…๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ GUI ์—†์ด๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. scapy.Sniffer Python ํŒจํ‚ค์ง€์—์„œ ์Šค ์บํ”ผ). ๋ฆฌ๋ณธ ๋„๊ตฌ ๋ชจ์Œ์„ ์‚ฌ์šฉํ•œ MS ๊ทธ๋ฆผํŒ ์ž๋™ํ™”์˜ ์˜ˆ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ ํ•™์ƒ์ด ์ž‘์„ฑํ•œ ๋˜ ๋‹ค๋ฅธ ํ›Œ๋ฅญํ•œ ์˜ˆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. Explorer.exe์—์„œ Google ๋“œ๋ผ์ด๋ธŒ์šฉ Chrome ํŽ˜์ด์ง€๋กœ ํŒŒ์ผ ๋“œ๋ž˜๊ทธ (์กฐ๊ธˆ ํ›„์— ๋ฉ”์ธ ์ €์žฅ์†Œ๋กœ ์ด๋™ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.)

๊ทธ๋ฆฌ๊ณ  ๋ฌผ๋ก  ํ‚ค๋ณด๋“œ(๋‹จ์ถ•ํ‚ค) ๋ฐ ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ์˜ˆ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
Hook_and_listen.py.

๊ฐ์‚ฌ์˜ ๋ง

ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ์— ์ง€์†์ ์œผ๋กœ ๋„์›€์„ ์ฃผ์‹œ๋Š” ๋ถ„๋“ค๊ป˜ ํŠน๋ณ„ํžˆ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋‚˜์™€ ์„ฑ ๋ฐœ๋ Œํƒ€์ธ ์ด๊ฒƒ์€ ์˜์›ํ•œ ์ทจ๋ฏธ์ž…๋‹ˆ๋‹ค. UNN์˜ ๋‚ด ํ•™์ƒ ์ค‘ ๋‘ ๋ช…์ด ์ตœ๊ทผ ์ด ์ฃผ์ œ์— ๋Œ€ํ•œ ํ•™์‚ฌ ํ•™์œ„๋ฅผ ์˜นํ˜ธํ–ˆ์Šต๋‹ˆ๋‹ค. ์•Œ๋ ‰์‚ฐ๋” MS UI ์ž๋™ํ™” ์ง€์›์— ํฐ ๊ธฐ์—ฌ๋ฅผ ํ–ˆ์œผ๋ฉฐ ์ตœ๊ทผ์—๋Š” ํ…์ŠคํŠธ ์†์„ฑ(๊ฐ€์žฅ ๋ณต์žกํ•œ ๊ธฐ๋Šฅ)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ "๊ธฐ๋ก ์žฌ์ƒ" ์›๋ฆฌ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ์ž๋™ ์ฝ”๋“œ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ค๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๊นŒ์ง€๋Š” "uia" ๋ฐฑ์—”๋“œ์—๋งŒ ํ•ด๋‹น๋ฉ๋‹ˆ๋‹ค. ์ด๋ฐ˜ AT-SPI(๋ชจ๋“ˆ) ๊ธฐ๋ฐ˜์˜ ์ƒˆ๋กœ์šด Linux์šฉ ๋ฐฑ์—”๋“œ๋ฅผ ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. mouse ะธ keyboard ~์— ๊ทผ๊ฑฐํ•˜์—ฌ ํŒŒ์ด์ฌ-xlib - ์ด๋ฏธ ๋ฆด๋ฆฌ์Šค 0.6.x์— ์žˆ์Œ).

์ œ๊ฐ€ Python์œผ๋กœ ์ž๋™ํ™”์— ๋Œ€ํ•œ ํŠน๊ฐ•์„ ๊ฝค ์˜ค๋žซ๋™์•ˆ ๊ฐ•์˜ํ•ด ์™”๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ถ€ ์„์‚ฌ ํ•™์ƒ๋“ค์€ ์ž๋™ํ™”์˜ ์ž‘์€ ๊ธฐ๋Šฅ์ด๋‚˜ ์˜ˆ์ œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ˆ™์ œ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ตฌ ๋‹จ๊ณ„์˜ ์ผ๋ถ€ ํ•ต์‹ฌ ์‚ฌํ•ญ๋„ ํ•œ๋•Œ ํ•™์ƒ๋“ค์— ์˜ํ•ด ๋ฐœ๊ตด๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋กœ๋Š” ์ฝ”๋“œ ํ’ˆ์งˆ์„ ์—„๊ฒฉํ•˜๊ฒŒ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ฝ 95%์˜ ์ฝ”๋“œ ์ ์šฉ ๋ฒ”์œ„๋ฅผ ๊ฐ–์ถ˜ ์ •์  ๋ถ„์„๊ธฐ(QuantifiedCode, Codacy ๋ฐ Landscape)์™€ ํด๋ผ์šฐ๋“œ(AppVeyor ์„œ๋น„์Šค)์˜ ์ž๋™ ํ…Œ์ŠคํŠธ๊ฐ€ ์ด์— ๋งŽ์€ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฆฌ๋ทฐ๋ฅผ ๋‚จ๊ฒจ์ฃผ์‹œ๊ณ , ๋ฒ„๊ทธ๋ฅผ ์ œ๊ธฐํ•˜์‹œ๊ณ , Pull Request๋ฅผ ๋ณด๋‚ด์ฃผ์‹œ๋Š” ๋ชจ๋“  ๋ถ„๋“ค๊ป˜๋„ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

์ถ”๊ฐ€ ๋ฆฌ์†Œ์Šค

์šฐ๋ฆฌ๋Š” ์งˆ๋ฌธ์— ๋Œ€ํ•œ ํ›„์† ์กฐ์น˜๋ฅผ ์ทจํ•ฉ๋‹ˆ๋‹ค StackOverflow์˜ ํƒœ๊ทธ (์ตœ๊ทผ์— ๋“ฑ์žฅํ•œ SO ๋Ÿฌ์‹œ์•„์–ด ๋ฒ„์ „์˜ ํƒœ๊ทธ) ๊ทธ๋ฆฌ๊ณ  ํ† ์Šคํ„ฐ์˜ ํ‚ค์›Œ๋“œ๋กœ. ์žˆ๋‹ค Gitter์˜ ๋Ÿฌ์‹œ์•„์–ด ์ฑ„ํŒ….

๋งค๋‹ฌ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค GUI ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์˜คํ”ˆ ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ฑ๊ธ‰. GitHub์˜ ๋ณ„ ์ˆ˜์™€ ๊ด€๋ จํ•˜์—ฌ Autohotkey(๋งค์šฐ ํฐ ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ ์˜ค๋žœ ์—ญ์‚ฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ)์™€ PyAutoGUI๋งŒ์ด ๋” ๋น ๋ฅด๊ฒŒ ์„ฑ์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Python' ๋“ฑ).

์ถœ์ฒ˜ : habr.com

DDoS ๋ณดํ˜ธ, VPS VDS ์„œ๋ฒ„๊ฐ€ ์žˆ๋Š” ์‚ฌ์ดํŠธ๋ฅผ ์œ„ํ•œ ์•ˆ์ •์ ์ธ ํ˜ธ์ŠคํŒ… ๊ตฌ์ž… ๐Ÿ”ฅ DDoS ๊ณต๊ฒฉ ๋ฐฉ์ง€ ๊ธฐ๋Šฅ์ด ํƒ‘์žฌ๋œ ์•ˆ์ •์ ์ธ ์›น์‚ฌ์ดํŠธ ํ˜ธ์ŠคํŒ…, VPS ๋ฐ VDS ์„œ๋ฒ„๋ฅผ ๊ตฌ๋งคํ•˜์„ธ์š” | ProHoster