Skip to content

Commit 8c9bf45

Browse files
committed
executing js
1 parent 46ec211 commit 8c9bf45

13 files changed

+276
-25
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ app [testCases] { r2e: platform "https://github.com/adomurad/r2e-platform/releas
3939
import r2e.Test exposing [test]
4040
import r2e.Browser
4141
import r2e.Element
42-
import r2e.Console
42+
import r2e.Debug
4343
4444
testCases = [
4545
test1,
@@ -50,7 +50,7 @@ test1 = test "test1" \browser ->
5050
input = browser |> Browser.findElement! (Css "#developer-name")
5151
input |> Element.click!
5252
53-
Console.printLine "I have clicked on a Element..."
53+
Debug.printLine "I have clicked on a Element..."
5454
```
5555

5656
## Local Development

TODO.md

-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
- pdf from elements/pages
88
- write to files (to save screenshots and pdfs)
99
- Env and Args
10-
- js execution
11-
- rename Console to Debug ? Debug.wait Debug.writeLine Debug.printLine Debug.log
12-
?
1310
- create json / xml / junit reporters
1411
- send parameters from Roc app to host, e.g. headless, browser type, version,
1512
timeouts, default window size, etc,

host/roc/app.go

+10
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ func roc_fx_browserGetScreenshot(sessionId *RocStr) C.struct_ResultVoidStr {
140140
}
141141
}
142142

143+
//export roc_fx_executeJs
144+
func roc_fx_executeJs(sessionId, jsString, argsStr *RocStr) C.struct_ResultVoidStr {
145+
result, err := webdriver.ExecuteJs(sessionId.String(), jsString.String(), argsStr.String())
146+
if err != nil {
147+
return createRocResultStr(RocErr, err.Error())
148+
} else {
149+
return createRocResultStr(RocOk, result)
150+
}
151+
}
152+
143153
// //export roc_fx_browserGetPdf
144154
// func roc_fx_browserGetPdf(sessionId *RocStr, width, height, top, bottom, left, right, scale float64, orientationStr *RocStr, shrinkToFit, background int64, pageRanges *RocList[RocStr]) C.struct_ResultVoidStr {
145155
// shrinkToFitBool := false

host/webdriver/webdriver.go

+42
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,48 @@ func BrowserGetScreenshot(sessionId string) (string, error) {
170170
return response.Value, nil
171171
}
172172

173+
type ExecuteJs_Response struct {
174+
Value interface{} `json:"value"`
175+
}
176+
177+
func ExecuteJs(sessionId, jsString, argsString string) (string, error) {
178+
requestUrl := fmt.Sprintf("%s/session/%s/execute/sync", baseUrl, sessionId)
179+
180+
jsEscaped, err := json.Marshal(jsString)
181+
if err != nil {
182+
return "", err
183+
}
184+
185+
jsonData := []byte(fmt.Sprintf(`{
186+
"script": %s,
187+
"args": %s
188+
}`, jsEscaped, argsString))
189+
190+
var response ExecuteJs_Response
191+
err = makeHttpRequest("POST", requestUrl, bytes.NewBuffer(jsonData), &response)
192+
if err != nil {
193+
return "", err
194+
}
195+
196+
switch v := response.Value.(type) {
197+
198+
case nil:
199+
return "", nil
200+
201+
case string:
202+
return v, nil
203+
204+
case bool:
205+
return strconv.FormatBool(v), nil
206+
207+
case float64:
208+
return strconv.FormatFloat(v, 'f', -1, 64), nil
209+
210+
default:
211+
return "", fmt.Errorf("unsupported type: %s", v)
212+
}
213+
}
214+
173215
// type PdfOptions struct {
174216
// Page PdfPageOptions
175217
// Margin PdfMarginOptions

platform/Browser.roc

+85
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@ module [
2121
maximizeWindow,
2222
minimizeWindow,
2323
fullScreenWindow,
24+
executeJs,
25+
executeJsWithOutput,
26+
executeJsWithArgs,
27+
2428
]
2529

2630
import Effect
31+
import CommonBrowser
2732
import Internal exposing [Browser, Element]
2833

2934
## Opens a new `Browser` window.
@@ -524,3 +529,83 @@ fullScreenWindow = \browser ->
524529
[xVal, yVal, widthVal, heightVal] -> { x: xVal, y: yVal, width: widthVal |> Num.toU32, height: heightVal |> Num.toU32 }
525530
_ -> crash "the contract with host should not fail"
526531
|> Task.mapErr WebDriverError
532+
533+
## Execute JavaScript in the `Browser`.
534+
##
535+
## ```
536+
## browser |> Browser.executeJs! "console.log('wow')"
537+
## ```
538+
executeJs : Browser, Str -> Task {} [WebDriverError Str, JsReturnTypeError Str] where a implements Decoding
539+
executeJs = \browser, script ->
540+
_output : Str
541+
_output = CommonBrowser.executeJs! browser script
542+
Task.ok {}
543+
544+
## Execute JavaScript in the `Browser` and get the response.
545+
##
546+
## This function can be used with types like: `Bool`, `Str`, `I64`, `F64`, etc.
547+
## R2E will try to cast the browser response to the choosen type.
548+
##
549+
## When the response is empty e.g. property does not exist, then the default value of the choosen type will be used:
550+
## - `Str` - ""
551+
## - `Bool` - Bool.false
552+
## - `Num` - 0
553+
##
554+
## The output will be casted to expected Roc type:
555+
##
556+
## ```
557+
## response = browser |> Browser.executeJsWithOutput! "return 50 + 5;"
558+
## response |> Assert.shouldBe! 55
559+
##
560+
## response = browser |> Browser.executeJsWithOutput! "return 50.5 + 5;"
561+
## response |> Assert.shouldBe! 55.5
562+
##
563+
## response = browser |> Browser.executeJsWithOutput! "return 50.5 + 5;"
564+
## response |> Assert.shouldBe! "55.5"
565+
##
566+
## response = browser |> Browser.executeJsWithOutput! "return true"
567+
## response |> Assert.shouldBe! "true"
568+
##
569+
## response = browser |> Browser.executeJsWithOutput! "return true"
570+
## response |> Assert.shouldBe! Bool.true
571+
## ```
572+
##
573+
## The function can return a `Promise`.
574+
executeJsWithOutput : Browser, Str -> Task a [WebDriverError Str, JsReturnTypeError Str] where a implements Decoding
575+
executeJsWithOutput = \browser, script ->
576+
CommonBrowser.executeJs browser script
577+
578+
JsValue : [String Str, Number F64, Boolean Bool, Null]
579+
580+
## Execute JavaScript in the `Browser` with arguments and get the response.
581+
##
582+
## This function can be used with types like: `Bool`, `Str`, `I64`, `F64`, etc.
583+
## R2E will try to cast the browser response to the choosen type.
584+
##
585+
## The arguments is a list of:
586+
##
587+
## ```
588+
## JsValue : [String Str, Number F64, Boolean Bool, Null]
589+
## ```
590+
##
591+
## When the response is empty e.g. property does not exist, then the default value of the choosen type will be used:
592+
## - `Str` - ""
593+
## - `Bool` - Bool.false
594+
## - `Num` - 0
595+
##
596+
## Args can only be used using the `arguments` array in js.
597+
##
598+
## The output will be casted to expected Roc type:
599+
##
600+
## ```
601+
## response = browser |> Browser.executeJsWithArgs! "return 50 + 5;" []
602+
## response |> Assert.shouldBe! 55
603+
##
604+
## response = browser |> Browser.executeJsWithArgs! "return 50.5 + 5;" [Number 55.5, String "5"]
605+
## response |> Assert.shouldBe! 55.5
606+
## ```
607+
##
608+
## The function can return a `Promise`.
609+
executeJsWithArgs : Browser, Str, List JsValue -> Task a [WebDriverError Str, JsReturnTypeError Str] where a implements Decoding
610+
executeJsWithArgs = \browser, script, arguments ->
611+
CommonBrowser.executeJsWithArgs browser script arguments

platform/CommonBrowser.roc

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
module [executeJs, executeJsWithArgs, JsValue]
2+
3+
import Internal exposing [Browser]
4+
import Effect
5+
import PropertyDecoder
6+
7+
JsValue : [String Str, Number F64, Boolean Bool, Null]
8+
9+
executeJs : Browser, Str -> Task a [WebDriverError Str, JsReturnTypeError Str] where a implements Decoding
10+
executeJs = \browser, script ->
11+
{ sessionId } = Internal.unpackBrowserData browser
12+
13+
resultStr = Effect.executeJs sessionId script "[]" |> Task.mapErr! WebDriverError
14+
resultUtf8 = resultStr |> Str.toUtf8
15+
16+
decoded : Result a _
17+
decoded = Decode.fromBytes resultUtf8 PropertyDecoder.utf8
18+
19+
when decoded is
20+
Ok val -> Task.ok val
21+
Err _ -> Task.err (JsReturnTypeError "unsupported return type from js: \"$(resultStr)\"")
22+
23+
executeJsWithArgs : Browser, Str, List JsValue -> Task a [WebDriverError Str, JsReturnTypeError Str] where a implements Decoding
24+
executeJsWithArgs = \browser, script, arguments ->
25+
{ sessionId } = Internal.unpackBrowserData browser
26+
27+
argumentsStr = arguments |> jsArgumentsToStr
28+
29+
resultStr = Effect.executeJs sessionId script argumentsStr |> Task.mapErr! WebDriverError
30+
resultUtf8 = resultStr |> Str.toUtf8
31+
32+
decoded : Result a _
33+
decoded = Decode.fromBytes resultUtf8 PropertyDecoder.utf8
34+
35+
when decoded is
36+
Ok val -> Task.ok val
37+
Err _ -> Task.err (JsReturnTypeError "unsupported return type from js: \"$(resultStr)\"")
38+
39+
jsArgumentsToStr : List JsValue -> Str
40+
jsArgumentsToStr = \args ->
41+
argsStr =
42+
args
43+
|> List.walk "" \state, arg ->
44+
when arg is
45+
# TODO escape json strings
46+
String str -> state |> Str.concat ",\"$(str)\""
47+
Number num -> state |> Str.concat ",$(num |> Num.toStr)"
48+
Null -> state |> Str.concat ",null"
49+
Boolean bool ->
50+
if bool then
51+
state |> Str.concat ",true"
52+
else
53+
state |> Str.concat ",false"
54+
55+
"[$(argsStr |> Str.dropPrefix ",")]"
56+
57+
expect
58+
input = []
59+
expected = "[]"
60+
output = jsArgumentsToStr input
61+
output == expected
62+
63+
expect
64+
input = [String "wow", Number 78.2, Number 0, Boolean Bool.true, Boolean Bool.false, Null]
65+
expected = "[\"wow\",78.2,0,true,false,null]"
66+
output = jsArgumentsToStr input
67+
output == expected

platform/Console.roc platform/Debug.roc

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Effect
66
## followed by a newline.
77
##
88
## ```
9-
## Console.printLine "Hello World"
9+
## Debug.printLine "Hello World"
1010
## ```
1111
printLine : Str -> Task {} []
1212
printLine = \str ->
@@ -23,7 +23,7 @@ printLine = \str ->
2323
##
2424
## ```
2525
## # wait for 3s
26-
## Console.wait 3000
26+
## Debug.wait 3000
2727
## ```
2828
wait : U64 -> Task {} []
2929
wait = \timeout ->

platform/Effect.roc

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ hosted Effect
3333
browserMaximize,
3434
browserMinimize,
3535
browserFullScreen,
36+
executeJs,
3637
]
3738
imports []
3839

@@ -86,6 +87,8 @@ browserMinimize : Str -> Task (List I64) Str
8687

8788
browserFullScreen : Str -> Task (List I64) Str
8889

90+
executeJs : Str, Str, Str -> Task Str Str
91+
8992
# element effects
9093
elementClick : Str, Str -> Task {} Str
9194

platform/InternalTest.roc

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module [test, runTests]
22

33
import Internal exposing [Browser]
4-
import Console
5-
import Time
4+
import Debug
5+
import Utils
66
import Browser
77
import BasicHtmlReporter
88
import InternalReporting
@@ -33,17 +33,17 @@ test = \name, testBody ->
3333
runTests = \testCases ->
3434
Assert.shouldBe! 1 1 # supressing the warning
3535

36-
Console.printLine! "Starting test run..."
36+
Debug.printLine! "Starting test run..."
3737

38-
startTime = Time.getTimeMilis!
38+
startTime = Utils.getTimeMilis!
3939

4040
results =
4141
testCases
4242
|> List.mapWithIndex \testCase, i ->
4343
runTest i testCase
4444
|> Task.sequence!
4545

46-
endTime = Time.getTimeMilis!
46+
endTime = Utils.getTimeMilis!
4747
duration = endTime - startTime
4848

4949
reporters = [BasicHtmlReporter.reporter]
@@ -65,29 +65,25 @@ runTest : U64, TestCase _ -> Task (TestCaseResult [WebDriverError Str, Assertion
6565
runTest = \i, @TestCase { name, testBody } ->
6666
indexStr = (i + 1) |> Num.toStr
6767

68-
Console.printLine! "" # empty line for readability
69-
Console.printLine! "$(color.gray)Test $(indexStr):$(color.end) \"$(name)\": Running..."
68+
Debug.printLine! "" # empty line for readability
69+
Debug.printLine! "$(color.gray)Test $(indexStr):$(color.end) \"$(name)\": Running..."
7070

71-
startTime = Time.getTimeMilis!
72-
# result = runTestSafe testBody |> Task.result!
71+
startTime = Utils.getTimeMilis!
7372
resultWithMaybeScreenshot = (runTestSafe testBody) |> Task.result!
7473

75-
endTime = Time.getTimeMilis!
74+
endTime = Utils.getTimeMilis!
7675
duration = endTime - startTime
7776

7877
{ result, screenshot } =
7978
when resultWithMaybeScreenshot is
8079
Ok {} -> { result: Ok {}, screenshot: NoScreenshot }
8180
Err (ResultWithoutScreenshot res) -> { result: Err res, screenshot: NoScreenshot }
8281
Err (ResultWithScreenshot res screenBase64) -> { result: Err res, screenshot: Screenshot screenBase64 }
83-
# result = Ok {}
84-
# screenshot = NoScreenshot
8582

8683
testCaseResult = {
8784
name,
8885
result,
8986
duration,
90-
# screenshot: NoScreenshot,
9187
screenshot,
9288
}
9389

@@ -104,7 +100,7 @@ runTest = \i, @TestCase { name, testBody } ->
104100
unhandledError ->
105101
"$(color.gray)Test $(indexStr):$(color.end) \"$(name)\": $(color.red)$(unhandledError |> Inspect.toStr)$(color.end)"
106102
107-
Console.printLine! resultLogMessage
103+
Debug.printLine! resultLogMessage
108104
109105
Task.ok testCaseResult
110106
@@ -136,8 +132,8 @@ takeConditionalScreenshot = \shouldTakeScreenshot, browser ->
136132
137133
printResultSummary : List (TestCaseResult _) -> Task.Task {} _
138134
printResultSummary = \results ->
139-
Console.printLine! "" # empty line
140-
Console.printLine! "Summary:"
135+
Debug.printLine! "" # empty line
136+
Debug.printLine! "Summary:"
141137
142138
totalCount = results |> List.len
143139
errorCount = results |> List.countIf \{ result } -> result |> Result.isErr
@@ -153,7 +149,7 @@ printResultSummary = \results ->
153149
else
154150
"$(color.green)$(msg)$(color.end)"
155151
156-
Console.printLine "$(msgWithColor)\n"
152+
Debug.printLine "$(msgWithColor)\n"
157153
158154
color = {
159155
gray: "\u(001b)[4;90m",
File renamed without changes.

platform/main.roc

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ platform ""
66
Browser,
77
Element,
88
Assert,
9-
Console,
9+
Debug,
1010
Error,
1111
Reporting,
1212
BasicHtmlReporter,

0 commit comments

Comments
 (0)