什么是RF
RF是一个基于 Python 的、可扩展的关键字驱动的自动化 验收测试框架、验收测试驱动开发 (ATDD)、 行为驱动开发 (BDD) 和机器人流程自动化 (RPA)。它 可用于分布式、异构环境,其中自动化 需要使用不同的技术和接口。
该框架周围有一个丰富的生态系统,由各种通用 作为单独项目开发的库和工具。查看更多 有关机器人框架和生态系统的信息,请参阅 http://robotframework.org。
为什么使用RF?
- 支持易于使用的表格语法,以便在统一的环境中创建测试用例 道路。
- 提供从 现有关键字。
- 以 HTML 格式提供易于阅读的结果报告和日志。
- 独立于平台和应用程序。
- 提供用于创建自定义测试库的简单库 API 可以使用 Python 本地实现。
- 提供命令行界面和基于 XML 的输出文件 集成到现有的构建基础架构中(持续集成 系统)。
- 为测试 Web 应用程序、rest API、移动应用程序提供支持 运行进程,通过 Telnet 或 SSH 连接到远程系统等。
- 支持创建数据驱动的测试用例。
- 内置对变量的支持,特别适用于测试 不同的环境。
- 提供标记以对要执行的测试用例进行分类和选择。
- 实现与源代码管理的轻松集成:测试套件只是文件 以及可以使用生产代码进行版本控制的目录。
- 提供测试用例和测试套件级别的设置和拆卸。
- 模块化架构支持创建测试,甚至对于具有 几种不同的接口。
环境准备
- 安装python
- 虚拟环境中安装robotframework
cd C:\projects
mkdir MyProject
cd MyProject
python -m venv .venv
.venv\Scripts\activate.bat
pip install robotframework
robot --version
3.pycharm安装插件
Robot Framework Language Server
为 Robot Framework 添加调试配置以运行当前测试套件
添加 Robot Framework 的调试配置以运行当前测试用例(通过选定的文本)
4、安装库文件
pip install --upgrade robotframework-seleniumlibrary
5、下载浏览器驱动,放置python路径的Scripts目录下。
Library
关于射频指南 |机器人框架 (robotframework.org)
使用RF需要使用Library,常用的第三方库如下:
在web浏览器中进行web应用程序测试可以使用的库是
- Selenium Library 在内部使用流行的 Selenium 工具的 Web 测试库
- Browser Library 由 Playwright 提供支持。以速度、可靠性和可见性为目标。
web service和restful API可以用的库是
- Requests Library 一个机器人框架库,旨在通过包装众所周知的 Python 请求库来提供 HTTP API 测试功能
标准库
RF标准库是Robot Framework(RF)在安装完成后自带的库。这些库无需额外安装,可以直接在RF中使用。其中,Builtin库是一个常用关键字库,它包含经常需要使用的关键字,无需导入即可直接使用。除Builtin库外,其他标准库如Dialogs、Collections、OperatingSystem、Remote、Screenshot和Process等,都需要通过import导入才能使用。这些库提供了丰富的功能,例如暂停测试执行和从用户获取输入、处理Python列表和字典、执行各种操作系统相关的任务、远程库接口的支持、捕获和存储桌面截图以及管理系统中运行的过程等。
标准库 |机器人框架 (robotframework.org)
selenium Library
https://robotframework.org/SeleniumLibrary/SeleniumLibrary.html
selenium Library是RF的一个web测试库,内部使用selenium。
pip install --upgrade robotframework-seleniumlibrary
新建一个test.robot文件,robot是RF测试用例文件的后缀。
首先在Settings导入SeleniumLibrary,然后在TestCase里写测试用例,如下例的Open Browser都是SeleniumLibrary提供的关键字,如下用例打开edge浏览器,输入框中输入关键字,并点击百度一下按钮,最后关闭浏览器。
*** Settings ***
Library SeleniumLibrary*** Test Cases ***
Search keywords in baiduOpen Browser url=https://www.baidu.com browser=edgeInput Text //input[@id="kw"] 周杰伦Click Element //input[@id="su"]Sleep 5Close Browser
导入Library失败
原因是python环境变量错了,电脑上有3.7的和3.8的,我用的3.8的,但是没有把3.8设置为环境变了
输入关键字,会有提示如图Input password,会提示让输入定位和密码。
Browser Library
这个库是由playwright支持的
Browser Library | ROBOT FRAMEWORK
pip install robotframework-browser
rfbrowser init
Installing playwright...
Installing playwright-chromium...
Installing playwright-firefox...
Installing playwright-webkit...
Done!
Requests Library
这个库可以进行API测试,基于Python的Requests
pip install robotframework-requests
*** Settings ***
Library RequestsLibrary*** Test Cases ***Quick Get Request Test${response}= GET https://www.google.comQuick Get Request With Parameters Test${response}= GET https://www.google.com/search params=query=ciao expected_status=200Quick Get A JSON Body Test${response}= GET https://jsonplaceholder.typicode.com/posts/1Should Be Equal As Strings 1 ${response.json()}[id]
运行用例
方法一:使用配置的run testsuite按钮。
方法二:
点击在测试文件里左侧的按钮,这里的按钮其实也是插件配置出的按钮。
运行后可以看到测试结果,并生成了html格式的测试报告。
测试报告
项目结构
Test Suites 可以组织在一个或多个robot文件里,可以放在tests/文件夹下
resources 资源文件,里面存放的是可重复使用的keywords,可以是robot文件或py文件,可以放在resources文件夹下,如
common.resource -存放一般的关键字,如Login/Logout,导航
search.robot,跟查询有关的关键字
util.py python相关的关键字
libraries 常用的python关键字库,可以存放在libraries/文件夹下,某些项目会区分libraries和resources,有些不会。
如
my_project
├── tests
│ ├── suiteA.robot
│ ├── suiteB.robot
│ ├── ...
│
├── resources
│ ├── common.resource
│ ├── some_other.resource
│ ├── custom_library.py
│ ├── variables.py
│ ├── ...
│
├── .gitlab-ci.yml
├── .gitignore
├── README.md
├── requirements.txt
执行用例
$ robot --pythonpath . tests
一些项目还会有data文件,存放测试数据
my_project
├── tests
│ ├── authentication
│ │ ├── login.robot
│ │ ├── ...
│ │
│ ├── master-data
│ │ ├── customers.robot
│ │ ├── products.robot
│ │ ├── ...
│ │
│ ├── order
│ │ ├── order_creation.robot
│ │ ├── order_processing.robot
│ │ ├── ...
│
├── resources
│ ├── common.resource
│ ├── search.resource
│ ├── master-data
│ │ ├── customers.resource
│ │ ├── products.resource
│ │ ├── ...
│ │
│ ├── ...
│
├── data
│ ├── master-data
│ │ ├── customers.py
│ │ ├── products.py
│ │ ├── ...
│ │
│ ├── order
│ │ ├── order_creation.yaml
│ │ ├── order_processing.yaml
│ │ ├── ...
│
├── .gitlab-ci.yml
├── .gitignore
├── README.md
├── requirements.txt
示例 项目
RF官网提供了几个例子
Examples Overview | ROBOT FRAMEWORK
Vehicle Insurance App
根据下面的例子可以看到,RF的测试文件,包含
*** Settings ***-用来引入库和资源
*** Variables *** 用来指定变量,在测试用例中可使用${}来引用。
*** Test Cases *** 下面为用例,其中用例Create Quote for Car下包含的关键字,都是在*** Keywords ***中自定义的。
*** Keywords *** 定义关键字,关键字下调用Browser库文件中的关键字
-
[Arguments] firstname=Max{lastname}=Mustermann
- 这行定义了这个关键字需要的参数及其默认值。在这个例子中,
Enter Insurant Data
关键字接受两个参数:firstname
和lastname
。如果调用这个关键字时没有提供这些参数的值,那么它们将分别默认为Max
和Mustermann
。
- 这行定义了这个关键字需要的参数及其默认值。在这个例子中,
*** Settings ***
Library Browser*** Variables ***
${BROWSER} chromium
${HEADLESS} false*** Test Cases ***
Create Quote for CarOpen Insurance ApplicationEnter Vehicle Data for AutomobileEnter Insurant DataEnter Product DataSelect Price OptionSend QuoteEnd Test*** Keywords ***
Open Insurance ApplicationNew Browser browser=${BROWSER} headless=${HEADLESS}New Context locale=en-GBNew Page http://sampleapp.tricentis.com/Enter Vehicle Data for AutomobileClick div.main-navigation >> "Automobile"Select Options By id=make text AudiFill Text id=engineperformance 110Fill Text id=dateofmanufacture 06/12/1980Select Options By id=numberofseats text 5Select Options By id=fuel text Petrol Fill Text id=listprice 30000Fill Text id=licenseplatenumber DMK1234Fill Text id=annualmileage 10000 Click section[style="display: block;"] >> text=Next »Enter Insurant Data[Arguments] ${firstname}=Max ${lastname}=MustermannFill Text id=firstname MaxFill Text id=lastname MustermannFill Text id=birthdate 01/31/1980Check Checkbox *css=label >> id=gendermaleFill Text id=streetaddress Test StreetSelect Options By id=country text GermanyFill Text id=zipcode 40123Fill Text id=city EssenSelect Options By id=occupation text EmployeeClick text=Cliff DivingClick section[style="display: block;"] >> text=Next »Enter Product DataFill Text id=startdate 06/01/2023Select Options By id=insurancesum text 7.000.000,00Select Options By id=meritrating text Bonus 1Select Options By id=damageinsurance text No CoverageCheck Checkbox *css=label >> id=EuroProtectionSelect Options By id=courtesycar text YesClick section[style="display: block;"] >> text=Next »Select Price Option[Arguments] ${price_option}=SilverClick *css=label >> css=[value=${price_option}]Click section[style="display: block;"] >> text=Next »Send QuoteFill Text "E-Mail" >> .. >> input max.mustermann@example.comFill Text "Phone" >> .. >> input 0049201123456Fill Text "Username" >> .. >> input max.mustermannFill Text "Password" >> .. >> input SecretPassword123!Fill Text "Confirm Password" >> .. >> input SecretPassword123!Fill Text "Comments" >> .. >> textarea Some comments${promise}= Promise To Wait For Response matcher=http://sampleapp.tricentis.com/101/tcpdf/pdfs/quote.php timeout=10Click "« Send »"${body}= Wait For ${promise}Log ${body}[status]Log ${body}[body]Wait For Elements State "Sending e-mail success!"Click "OK"End TestClose ContextClose Browser
WFA login
这个例子中引用了py文件中的函数
另外在Settings里可以使用Suite Setup和Suite Teardown进行test suite级别的测试数据准备和清理
Test Setup和Suite Teardown进行test级别前置和后置准备。
*** Settings ***
Library Browser
Library totp.py
Suite Setup New Browser browser=${BROWSER} headless=${HEADLESS}
Test Setup New Context
Test Teardown Close Context
Suite Teardown Close Browser*** Variables ***
${BROWSER} chromium
${HEADLESS} False*** Test Cases ***
Login with MFANew Page https://seleniumbase.io/realworld/loginFill Text id=username demo_userFill Text id=password secret_pass${totp} Get Totp GAXG2MTEOR3DMMDGFill Text id=totpcode ${totp}Click "Sign in"Get Text h1 == Welcome!
import pyotpdef get_totp(secret):totp = pyotp.TOTP(secret)return totp.now()
Restful Booker
*** Settings ***
Library RequestsLibrary
Library Collections
Suite Setup Authenticate as Admin*** Test Cases ***
Get Bookings from Restful Booker${body} Create Dictionary firstname=John${response} GET https://restful-booker.herokuapp.com/booking ${body}Status Should Be 200Log List ${response.json()}FOR ${booking} IN @{response.json()}${response} GET https://restful-booker.herokuapp.com/booking/${booking}[bookingid]TRYLog ${response.json()}EXCEPTLog Cannot retrieve JSON due to invalid dataENDENDCreate a Booking at Restful Booker${booking_dates} Create Dictionary checkin=2022-12-31 checkout=2023-01-01${body} Create Dictionary firstname=Hans lastname=Gruber totalprice=200 depositpaid=false bookingdates=${booking_dates}${response} POST url=https://restful-booker.herokuapp.com/booking json=${body}${id} Set Variable ${response.json()}[bookingid]Set Suite Variable ${id}${response} GET https://restful-booker.herokuapp.com/booking/${id}Log ${response.json()}Should Be Equal ${response.json()}[lastname] GruberShould Be Equal ${response.json()}[firstname] HansShould Be Equal As Numbers ${response.json()}[totalprice] 200Dictionary Should Contain Value ${response.json()} GruberDelete Booking${header} Create Dictionary Cookie=token\=${token}${response} DELETE url=https://restful-booker.herokuapp.com/booking/${id} headers=${header}Status Should Be 201 ${response}*** Keywords ***
Authenticate as Admin${body} Create Dictionary username=admin password=password123${response} POST url=https://restful-booker.herokuapp.com/auth json=${body}Log ${response.json()}${token} Set Variable ${response.json()}[token]Log ${token}Set Suite Variable ${token}
todo MVC
这是一个BDD的例子
*** Settings ***
Library Browser
Library String
Suite Setup New Browser browser=${BROWSER} headless=${HEADLESS}
Test Setup New Context viewport={'width': 1920, 'height': 1080}
Test Teardown Close Context
Suite Teardown Close Browser*** Variables ***
${BROWSER} chromium
${HEADLESS} False*** Test Cases ***
Add Two ToDos And Check Items[Documentation] Checks if ToDos can be added and ToDo count increases[Tags] Add ToDoGiven ToDo App is openWhen I Add A New ToDo "Learn Robot Framework"And I Add A New ToDo "Write Test Cases"Then Open ToDos should show "2 items left"Add Two ToDos And Check Wrong Number Of Items[Documentation] Checks if ToDos can be added and ToDo count increases[Tags] Add ToDoGiven ToDo App is openWhen I Add A New ToDo "Learn Robot Framework"And I Add A New ToDo "Write Test Cases"Then Open ToDos should show "1 items left"Add ToDo And Mark Same ToDo[Tags] Mark ToDoGiven ToDo App is openWhen I Add A New ToDo "Learn Robot Framework"And I Mark ToDo "Learn Robot Framework"Then Open ToDos should show "0 items left"Check If Marked ToDos are removedGiven ToDo App is openAnd I Added Two ToDosWhen I Mark One ToDoThen Open ToDos should show "1 item left"Split ToDosGiven ToDo App is openWhen I Add New ToDos "Learn Robot Framework&Write Test Cases&Sleep"Then Open ToDos should show "3 items left"Add A Lot Of TodosGiven ToDo App is openWhen I Add "100" ToDosThen Open ToDos should show "100 items left"Add A Lot Of Todos With WHILEGiven ToDo App is openWhen I Add "100" ToDos With WHILE LoopThen Open ToDos should show "100 items left"*** Keywords ***
ToDo App is openNew Page https://todomvc.com/examples/react/I Add A New ToDo "${todo}" Fill Text .new-todo ${todo}Press Keys .new-todo EnterI Add New ToDos "${todo}"IF "&" in $todo@{todos} Split String ${todo} separator=&FOR ${item} IN @{todos}Fill Text .new-todo ${item}Press Keys .new-todo Enter ENDELSEFill Text .new-todo ${todo}Press Keys .new-todo EnterENDOpen ToDos should show "${text}"Get Text span.todo-count == ${text}I Mark ToDo "${todo}"Click "${todo}" >> .. >> input.toggleI Added Two ToDosI Add A New ToDo "Learn Robot Framework"I Add A New ToDo "Write Test Cases"I Mark One ToDoClick li:first-child >> input.toggleI Add "${count}" ToDosFOR ${index} IN RANGE ${count}I Add A New ToDo "My ToDo Number ${index}" ENDI Add "${count}" ToDos With WHILE Loop${x}= Set Variable ${0}WHILE ${x} < ${count}${x}= Evaluate ${x} + 1I Add A New ToDo "My ToDo Number ${x}"END
风格指南
风格指南 |机器人框架 (robotframework.org)
Sections
*** Comments ****** Settings ****** Variables ****** Test Cases ****** Keywords ***
Settings
*** Settings ***
Documentation
MetadataLibrary BuiltIn
Library 3rd Party
Library Custom
Resource
VariablesSuite Setup
Suite Teardown
Test Setup
Test Teardown
Test Template
Test TimeoutTest Tags
Variables
*** Variables ***
${VARIABLE} This is a Variable
${COMPOSITE VARIABLES} ${VARIABLE} with other variables.
Test Cases
Test Case[Documentation][Tags][Timeout][Setup][Template]Static Variable AssignmentsKeyword CallsVerification Keyword Call[Teardown]
Keyword
Keyword[Documentation][Tags][Arguments][Timeout][Setup]Static Variable AssignmentsKeyword Calls[Teardown]
扩展RF
可以写Python库
Static Library
静态库中RF的关键字被定义为python的方法。
Static Library With a Class
class DemoLibrary:def __init__(self, *args, **kwargs):print(f"Sample Library initialized with args: {args} and kwargs: {kwargs}")def my_keyword(self, *args, **kwargs):print(f"Keyword got args: {args} and kwargs: {kwargs}")return "Hello World"
*** Settings ***
Library DemoLibrary.py*** Test Cases ***
Use a Keyword with multiple argumentsMy Keyword Argument 1 Argument 2 Named Argument=One Value
Static Library withouth a Class
将关键字定义在python方法中
import base64def encode_as_base64(string):"""Encode string as base64."""return base64.b64encode(string.encode())def decode_from_base64(string):"""Decode string from base64."""return base64.b64decode(string).decode()
*** Settings ***
Library LibraryWithoutClass.py*** Test Cases ***
Use Custom Keywords${base64} Encode As Base64 This is a Test StringLog ${base64}${decoded} Decode From Base64 ${base64}Log ${decoded}
Decorators
可以使用装饰@@keyword和@not_keyword将方法装饰为关键字。
from robot.api.deco import keyword, not_keyword@keyword('Login via user panel')
def login(username, password):# ...@not_keyword
def this_is_not_keyword():pass
from robot.api.deco import keyword@keyword(tags=['tag1', 'tag2'])
def login(username, password):# ...@keyword('Custom name', ['tags', 'here'])
def another_example():# ...@keyword(types={'count': int, 'case_insensitive': bool})
def example_keyword(count, case_insensitive=True):if case_insensitive:# ...@keyword(types=[int, bool])
def example_keyword(count, case_insensitive=True):if case_insensitive:# ...
BDD
BDD (Behavior Driven Development) | ROBOT FRAMEWORK
变量
*** Variables *** Section
定义在这个部分的变量可以用于同一个文件中的所有的test cases和关键字中。
定义在这个部分的变量是suite变量。
如果一个resource或者robot文件中有variables部分被导入到test suite中,则这些变量也成为suite变量。
*** Variables ***
${my_var} my_value
@{my_list} Apple Banana Orange
&{my_dict} name=my_value1 password=my_value2*** Test Cases ***
Test Case 1Log ${my_var}Log ${my_list}Log ${my_dict}FOR ${item} IN @{my_list}Log ${item}ENDLog ${my_dict}[name]Log ${my_dict}[password]FOR ${key} ${value} IN &{my_dict}Log Many ${key} ${value}ENDMy Keyword*** Keywords ***
My KeywordLog Many ${my_var} ${my_list} ${my_dict}
在测试用例和关键字中设置变量
除了 *** 变量 *** 部分,还可以在测试用例和关键字中动态设置变量。 变量由关键字的返回值设置。 有一些关键字显式设置变量值,例如
- 设置变量
- 设置测试变量
- 设置套件变量
- 设置全局变量
它们可用于设置变量的值或更改变量的范围。
变量有不同的级别。
*** Test Cases ***Test Case 1${my_local_var} Set Variable Hello World #用例级别的变量Log ${my_local_var} # Pass: Logs the value of the variableSet Suite Variable ${my_suite_var} I'm a suite variable #套件级别变量Set Global Variable ${my_global_var} I'm a global variable #全局变量Test Case 2Log ${my_local_var} # Fails: 这个变量的范围仅存在Test Case1中Log ${my_suite_var} # Pass: Variable exists for the scope of the whole suiteLog ${my_global_var} # Pass: Variable exists for the scope of the whole test runTest Case 3My KeywordLog ${my_keyword_var} # Fails: 变量的范围为关键字My Keyword中Log ${my_test_var} # Pass: Variable exists in the scope of the test case*** Keywords ***
My Keyword${my_keyword_var} Set Variable Hello KeywordLog ${my_keyword_var} # Pass: Logs the value of the variableSet Test Variable ${my_test_var} I'm a test case variable
何时使用 $ 和 @ 以及 & 和 %?
变量名称包括
- 变量类型标识符(例如 、 、 、
$
@
&
%
) - 大括号
{}
- 变量名称(例如 , , ,
my_var
my_list
my_dict
my_var2
)
变量类型标识符用于定义变量的类型。
$
用于标量变量。@
用于列表变量。&
用于字典变量。%
用于环境变量。
标量变量、列表变量和字典有什么区别?
标量变量只能包含一个值。
一个列表变量可以包含多个值。
一个字典变量可以包含多个键值对。
但是,为什么我们可以用语法${my_list}
和语法@{my_list}
来访问列表变量呢?my_list
${my_list}
访问整个 List 对象。它是一个容器,包含列表中的所有项目。 您可以使用它来访问整个列表变量,或者使用语法 访问列表的特定项。my_list
my_list
${my_list}[index]
@{my_list}
访问 list 变量 的项。这就像列表变量被解压缩,所有项目都可以作为单独的变量使用。my_list
my_list
*** Settings ***
Library Collections
*** Variables ***
@{my_list} Apple Banana Orange*** Test Cases ***
Test Case 1Log ${my_list} # Pass: 打印整个列表对象 ['Apple', 'Banana', 'Orange']Log Many @{my_list} # Pass: Logs the items of the list object 分别返回Apple Banana OrangeLog Many ${my_list} # Pass: 打印整个列表对象Log List ${my_list} # Pass: 打印整个列表对象Log ${my_list}[0] # Pass: 打印列表对象的第一个元素Log @{my_list}[0] # Fail: @{my_list} 不是列表对象,不存在indexLog @{my_list} # Fail: The second argument of the Log keyword `level` only allows the values `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `NONE`.# The value `Banana` for the argument `level` is not allowed.Log ${my_list}[0] ${my_list}[1] ${my_list}[2] # Fail: Same as aboveFOR ${item} IN @{my_list} # Pass: Iterates over the items of the list objectLog ${item} # Pass: 返回列表对象的元素END
每当您想要访问列表变量的容器时,都必须使用语法${my_list}
。
当您要将列表变量分解为其项时,您必须使用语法@{my_list}
.
这就像一个装有瓶子的啤酒箱。
使用${beer_crate}
该语法,您可以访问整个啤酒箱。
使用语法@{beer_crate}
,您可以得到瓶子。
使用语法[index]
,您可以在板条箱内获得一个特定的瓶子。${beer_crate}
变量文件
可以从外部文件加载变量,如 Python的py或yaml文件。
对于 Yaml 文件,pyyaml是必需的。
pip install pyyaml
可以在py文件中创建简单的变量、列表或字典,甚至是复杂的对象,如类。
PythonVariables.py
class TestEnv:ip = '123.4.5.6'user = 'robot'roles = ['admin', 'user']my_var = 'Hello World'
my_list = ["Apple", "Banana", "Cherry"]
my_dict = {'name': 'John', 'age': 36}
DynamicVariables.py
import os
import random
import time
import mathUSER = os.getlogin() # current login name
RANDOM_INT = random.randint(0, 10) # random integer in range [0,10]
CURRENT_TIME = time.asctime() # timestamp like 'Thu Apr 6 12:45:21 2006'
if time.localtime()[3] > 12:AFTERNOON = True
else:AFTERNOON = Falsedef get_area(diameter):radius = diameter / 2area = math.pi * radius * radiusreturn areaAREA1 = get_area(1)
AREA2 = get_area(2)
.yaml或.yml文件解释为字典
YamlVariables.yml
# These are scalars:
base_url: https://qaserver.mycompany.com:8080
admin_user: iAmAdMiN
admin_password: eieioscoobydoo1234
# Now, a list:
yaml_list:- Item 1- Item two- 3
# Finally, a dictionary:
yaml_dict:key_1: A stringkey_2: 1 # an int
*** Settings ***
Variables PythonVariables.py
Variables YamlVariables.yaml
Variables DynamicVariables.py*** Test Cases ***
Access Python VariablesLog ${TestEnv.ip}Log ${TestEnv.roles}Log Many @{TestEnv.roles}Log ${my_var}Log Many @{my_list}Log Many &{my_dict}FOR ${item} IN @{my_list}Log ${item}ENDAccess Yaml VariablesLog ${base_url}Log Many @{yaml_list}Log ${yaml_dict}Log ${yaml_dict}[key_1]Access Dynamic VariablesLog ${USER}Log ${RANDOM_INT}Log ${CURRENT_TIME}Log ${AFTERNOON}Log ${AREA1}Log ${AREA2}