Best JavaScript code snippet using devicefarmer-stf
index.js
Source:index.js
1import React from 'react';2import MapView from './view/map';3import { Button, message } from 'antd';4import { withRouter } from 'react-router-dom';5import IconFont from '../../components/IconFont';6import ListView from './view/list';7import ResourceTreeView from '../BusinessComponent/ResouceTree';8import MapMarkerVideo from '../BusinessComponent/MapMarkerVideo';9import PropTypes from 'prop-types';10import { observer } from 'mobx-react';11import { BusinessProvider } from '../../utils/Decorator/BusinessProvider';12import VideoLoopSetting from './components/VideoLoopSetting';13import videoScreen from '../../libs/Dictionary/videoScreen';14import GroupModal from './components/GroupModal';15import { exitFullscreen } from '../../utils/FullScreen';16import LogsComponent from '../../components/LogsComponent';17import './style/index.scss';18@withRouter19@BusinessProvider(20 'OrgStore',21 'DeviceStore',22 'VideoSurveillanceStore',23 'UserGroupStore',24 'TabStore'25)26@LogsComponent()27@observer28export default class VideoSurveillance extends React.Component {29 static childContextTypes = {30 isMapMode: PropTypes.bool,31 selectDevice: PropTypes.array,32 onSelectDevice: PropTypes.func,33 setDeviceListForCurrentPlayerBox: PropTypes.func,34 startVideoLoop: PropTypes.func,35 endVideoLoop: PropTypes.func,36 showLoopSettingLayout: PropTypes.func,37 closeLoopSettingLayout: PropTypes.func,38 playBoxConfig: PropTypes.array,39 isLoop: PropTypes.bool,40 loopOrgInfo: PropTypes.object,41 loopGroupName: PropTypes.string,42 deleteGroupDevice: PropTypes.func,43 addGroupDevice: PropTypes.func,44 showGroupModal: PropTypes.func,45 goPage: PropTypes.func,46 deleteGroup: PropTypes.func47 };48 getChildContext() {49 return {50 isMapMode: this.state.mapMode, //æ¯å¦å°å¾æ¨¡å¼51 selectDevice: this.state.selectDevice, //éä¸ç设å¤éå52 onSelectDevice: this.onSelectDevice, //éä¸è®¾å¤åæ§è¡çé»è¾53 setDeviceListForCurrentPlayerBox: this.setDeviceListForCurrentPlayerBox, //对æ¯ææ¾ä¸ç设å¤ï¼å¹é
å½åéä¸è®¾å¤å表ï¼å»é¤æ æç设å¤éä¸ç¶æ54 startVideoLoop: this.startVideoLoop, //55 endVideoLoop: this.endVideoLoop, //56 showLoopSettingLayout: this.showLoopSettingLayout, //57 closeLoopSettingLayout: this.closeLoopSettingLayout, //58 playBoxConfig: [], //对äºè½®å·¡ççªå£é
ç½®59 isLoop: this.state.isLoop, //轮巡çç¶æ60 loopOrgInfo: this.state.loopOrgInfo,61 loopGroupName: this.state.loopGroupName,62 deleteGroupDevice: this.deleteGroupDevice,63 addGroupDevice: this.addGroupDevice,64 showGroupModal: this.showGroupModal,65 goPage: this.goPage,66 deleteGroup: this.deleteGroup67 };68 }69 constructor(props) {70 super(props);71 const { location } = this.props;72 this.mapViewRef = React.createRef();73 this.listViewRef = React.createRef();74 this.playerDatas = []; //å½åææ¾çè§é¢å表75 //TODO 轮巡ç¸å
³76 this.loopList = []; //轮巡设å¤å表77 this.loopInterval = 1000; //轮巡é´éæ¶é´78 this.loopVideoBox = null; //å¯è½®å·¡çææ¾å®¹å¨é
ç½®79 this.loopListNumber = 1; //轮巡设å¤ç页æ°80 this.loopTimer = null; //轮巡å®æ¶å¨81 this.loopOneListSize = 0; //æ¯æ¬¡è½®å·¡çæ°é82 //TODO è·å页é¢åæ°83 let mapMode = true;84 if (location.state && location.state.pageState) {85 const { pageState = {} } = location.state;86 const { selectIds = [] } = pageState;87 let currentVideoScreen = videoScreen[1];88 if (selectIds.length > 4) {89 currentVideoScreen = videoScreen[2];90 }91 if (selectIds.length > 9) {92 currentVideoScreen = videoScreen[3];93 }94 if (currentVideoScreen !== videoScreen[1]) {95 this.selectSrceen(currentVideoScreen);96 }97 mapMode = !(pageState.mapMode === false);98 }99 this.state = {100 mapMode,101 isSlide: true,102 selectDevice: [],103 isLoop: false,104 showLoopSetting: false,105 loopOrgInfo: {},106 loopGroupName: null,107 showGroup: false,108 currentGroup: null,109 groupModalKey: Math.random(),110 loopModalKey: Math.random()111 };112 }113 componentDidMount() {114 const { DeviceStore, location } = this.props;115 if (location.state && location.state.pageState) {116 const { pageState = {} } = location.state;117 const { selectIds = [] } = pageState;118 const list = DeviceStore.queryCameraListByIds(selectIds);119 if (list.length > 0) {120 this.setState({ selectDevice: list }, () => {121 this.listViewRef.current.selectDevice(list);122 });123 }124 }125 }126 componentWillUnmount() {127 clearInterval(this.loopTime);128 setTimeout(() => {129 this.mapViewRef = null;130 this.listViewRef = null;131 this.playerDatas = null;132 this.loopList = null;133 this.loopInterval = null;134 this.loopVideoBox = null;135 this.loopListNumber = null;136 this.loopTimer = null;137 this.loopOneListSize = null;138 }, 60);139 }140 /**141 * éè左侧æ å½¢æ§ä»¶142 */143 slideAction = () => {144 const { isSlide } = this.state;145 this.setState({ isSlide: !isSlide });146 };147 /**148 * æ¾ç¤ºè½®å·¡çé
ç½®Modal è·å轮巡çæåæºå表149 * @param {object} item ç»ç»ä¿¡æ¯150 * @param {boolean} isGroup æ¯å¦ä»åç»è¿æ¥151 */152 showLoopSettingLayout = (item, isGroup) => {153 const { isLoop } = this.state;154 if (isLoop) {155 return message.warn('å½åæ£å¨æ§è¡è½®å·¡ä»»å¡ï¼');156 }157 if (isGroup) {158 this.loopList = item.deviceList;159 this.setState({160 showLoopSetting: true,161 loopGroupName: item.groupName,162 loopModalKey: Math.random()163 });164 } else {165 const { DeviceStore } = this.props;166 this.loopList = DeviceStore.queryCameraByIncludeOrgId(item.id);167 this.setState({168 showLoopSetting: true,169 loopOrgInfo: item,170 loopModalKey: Math.random()171 });172 }173 };174 /**175 * å
³é轮巡é
ç½®çªå£176 */177 closeLoopSettingLayout = () => {178 this.setState(179 {180 showLoopSetting: false,181 loopOrgInfo: {},182 loopGroupName: null183 },184 () => {185 setTimeout(() => this.setState({ loopModalKey: Math.random() }), 500);186 }187 );188 };189 /**190 * å¼å§è½®å·¡191 */192 startVideoLoop = ({ loopInterval, loopScreen, loopVideoBox }) => {193 this.setState({ showLoopSetting: false });194 this.selectSrceen(loopScreen);195 this.loopInterval = loopInterval;196 this.loopVideoBox = loopVideoBox;197 this.loopListNumber = 1;198 this.loopOneListSize = this.loopVideoBox.filter(v => v.isLoop).length;199 this.listViewRef.current.setLoopVideBox(this.loopVideoBox);200 this.setState({ isLoop: true });201 this.setCurrentLoopList();202 this.loopTimer = setInterval(() => {203 this.loopListNumber++;204 Array.isArray(this.loopList) && this.setCurrentLoopList();205 }, this.loopInterval + 1000);206 };207 /**208 * 设置å½åéè¦è½®å·¡ç设å¤209 */210 setCurrentLoopList() {211 const { selectDevice } = this.state;212 let startIndex = (this.loopListNumber - 1) * this.loopOneListSize;213 if (this.loopList.length <= this.loopOneListSize) {214 this.loopListNumber = 0;215 } else {216 if (startIndex + this.loopOneListSize >= this.loopList.length) {217 startIndex = this.loopList.length - this.loopOneListSize;218 this.loopListNumber = 0;219 }220 }221 let list = this.loopList.slice(222 startIndex,223 startIndex + this.loopOneListSize224 );225 this.listViewRef.current.playMethodForLoopDevice(list);226 this.setState({ selectDevice: selectDevice.concat(list) });227 }228 /**229 * ç»æ轮巡230 */231 endVideoLoop = () => {232 this.loopList = [];233 this.loopListNumber = 1;234 this.loopVideoBox.forEach(item => {235 item.isLoop = false;236 });237 this.listViewRef.current.setLoopVideBox(this.loopVideoBox);238 clearInterval(this.loopTimer);239 this.setState({240 isLoop: false,241 loopOrgInfo: {},242 loopGroupName: null243 });244 message.success('ç»æ轮巡ï¼');245 };246 /**247 * éä¸è®¾å¤åï¼æ§è¡çé»è¾248 */249 onSelectDevice = item => {250 const { mapMode } = this.state;251 if (mapMode) {252 //TODO å°å¾æ¨¡å¼åªè½éä¸ä¸ä¸ªè®¾å¤253 this.setState({ selectDevice: [item] });254 this.mapViewRef.current.wrappedInstance.markerClick(item);255 } else {256 //TODO å表模å¼å¯éä¸å¤ä¸ªè®¾å¤257 const { selectDevice } = this.state;258 //TODO å½å设å¤å·²ç»å¨ææ¾è§é¢ï¼ä¸åä»»ä½æä½259 const isPlaying = selectDevice.findIndex(v => v.id === item.id) > -1;260 if (isPlaying) {261 return false;262 }263 selectDevice.push(item);264 this.setState({ selectDevice });265 this.listViewRef.current.selectDevice([item]);266 }267 };268 /**269 * åæ¢æ¨¡å¼æ¸
空已éä¸ç设å¤270 */271 changeModeBtn = () => {272 const { mapMode, isLoop } = this.state;273 exitFullscreen();274 if (isLoop) {275 this.endVideoLoop();276 }277 this.playerDatas = [];278 this.setState({279 mapMode: !mapMode,280 selectDevice: []281 });282 };283 /**284 * æ¹æ³æååï¼play容å¨è¿åï¼ææ¾ä¿¡æ¯éå对æ¯éä¸è®¾å¤ï¼å é¤æ æéä¸ç设å¤285 */286 setDeviceListForCurrentPlayerBox = playerDatas => {287 const { selectDevice } = this.state;288 this.playerDatas = playerDatas;289 //TODO åç»ä»¶åé¦ææ¾å®¹å¨æ°æ®åï¼æ ¸å¯¹éä¸ç设å¤290 let list = selectDevice291 .map(item => {292 const isHas =293 this.playerDatas294 .filter(v => !!v)295 .findIndex(296 v => v.id === item.manufacturerDeviceId || v.id === item.id297 ) > -1;298 return isHas ? item : null;299 })300 .filter(v => !!v);301 this.setState({ selectDevice: list, loopModalKey: Math.random() });302 };303 /**304 * åæ¢å±å¹æ°é305 */306 selectSrceen = item => {307 const { VideoSurveillanceStore } = this.props;308 VideoSurveillanceStore.setVideoScreen(item);309 };310 /**311 * å
³éæé®åï¼æ¸
空éä¸ç设å¤312 */313 clearSelectDevice = () => {314 this.playerDatas = [];315 this.setState({ selectDevice: [], loopModalKey: Math.random() });316 };317 /**318 * å é¤æ¶èä¸ç设å¤319 */320 deleteGroupDevice = item => {321 const { UserGroupStore } = this.props;322 return UserGroupStore.edit(item);323 };324 /**325 * æä¾åç»ä»¶æ°å¼é¡µç¾çæ¹æ³326 */327 goPage = options => {328 const { history, TabStore } = this.props;329 TabStore.goPage({330 history,331 ...options332 });333 };334 /**335 * æ°å¢æ¶èä¸ç设å¤336 */337 addGroupDevice = items => {338 const { UserGroupStore } = this.props;339 return UserGroupStore.editDevice(items, true);340 };341 /**342 * æå¼åç»å¼¹çª343 */344 showGroupModal = (isEdit, group) => {345 this.setState({346 showGroup: true,347 currentGroup: isEdit ? group : null,348 groupModalKey: Math.random()349 });350 };351 /**352 * å
³éåç»å¼¹çª353 */354 hideGroupModal = () => {355 this.setState({ showGroup: false });356 };357 /**358 * æ°å¢åç»359 */360 addOrEditGroup = (isEdit, name, list, group) => {361 const { UserGroupStore } = this.props;362 let deviceIds = list.map(v => `${v.manufacturerDeviceId}/${v.deviceName}`);363 if (isEdit) {364 console.log(isEdit, name, list, group);365 UserGroupStore.editGroup(366 { groupName: group.groupName },367 { groupName: name, deviceIds }368 ).then(() => {369 this.hideGroupModal();370 message.success('æä½æåï¼');371 });372 } else {373 UserGroupStore.add({374 groupName: name,375 deviceIds376 }).then(() => {377 this.hideGroupModal();378 message.success('æä½æåï¼');379 });380 }381 };382 cancelAddGroup = () => {383 this.setState({ showGroup: false });384 };385 /**386 * æ°å¢æ¶èä¸ç设å¤387 */388 deleteGroup = name => {389 const { UserGroupStore } = this.props;390 return UserGroupStore.delete({ groupName: name });391 };392 render() {393 const {394 VideoSurveillanceStore,395 DeviceStore,396 UserGroupStore,397 OrgStore398 } = this.props;399 const {400 isSlide,401 mapMode,402 showLoopSetting,403 showGroup,404 currentGroup,405 groupModalKey,406 loopModalKey407 } = this.state;408 return (409 <div className="video-surveillance">410 <div className={`left-tree ${isSlide ? 'left-tree-slide' : ''}`}>411 <div className="slide-layout-left-tree">412 <ResourceTreeView413 deviceList={DeviceStore.cameraArray}414 collectionList={UserGroupStore.list}415 orgList={OrgStore.orgArray}416 />417 </div>418 <span className="slider-btn" onClick={this.slideAction}>419 <IconFont420 type={421 isSlide422 ? 'icon-Arrow_Small_Left_Mai'423 : 'icon-Arrow_Small_Right_Ma'424 }425 theme="outlined"426 />427 </span>428 </div>429 <div className="right-content">430 <Button431 type="primary"432 className="change-mode-btn orange-btn"433 onClick={this.changeModeBtn}434 >435 <IconFont type={mapMode ? 'icon-List_Map_Main' : 'icon-Map_Main'} />436 {mapMode ? 'åå±æ¨¡å¼' : 'å°å¾æ¨¡å¼'}437 </Button>438 {mapMode ? (439 <MapMarkerVideo ref={this.mapViewRef} logData={{isOther: false}}>440 <MapView onSelectDevice={this.onSelectDevice} />441 </MapMarkerVideo>442 ) : (443 <ListView444 closeVideo={this.clearSelectDevice}445 asyncGetCurrentVideoList={DeviceStore.asyncGetCurrentVideoList}446 asyncGetHistoryVideo={DeviceStore.asyncGetHistoryVideo}447 ref={this.listViewRef}448 selectSrceen={this.selectSrceen}449 currentScreen={VideoSurveillanceStore.currentVideoScreen}450 />451 )}452 </div>453 <VideoLoopSetting454 playerDatas={this.playerDatas}455 startVideoLoop={this.startVideoLoop}456 closeLoopSettingLayout={this.closeLoopSettingLayout}457 showLoop={showLoopSetting}458 key={loopModalKey}459 currentScreen={VideoSurveillanceStore.currentVideoScreen}460 loopListSize={this.loopList.length}461 />462 <GroupModal463 onOk={this.addOrEditGroup}464 visible={showGroup}465 onCancel={this.hideGroupModal}466 key={groupModalKey}467 group={currentGroup}468 />469 </div>470 );471 }...
CollectionList.js
Source:CollectionList.js
...38 groupName,39 deviceKey: `${item.manufacturerDeviceId}/${item.deviceName}`40 };41 this.context42 .deleteGroupDevice(data)43 .then(() => message.success('æä½æåï¼'));44 }45 deleteGroup(e, name) {46 stopPropagation(e);47 const { deleteGroup } = this.context;48 confirm({49 title: 'æ示',50 content: `ç¡®å®å é¤åç»â${name}â`,51 onOk() {52 return deleteGroup(name).then(() => message.success('æä½æåï¼'));53 },54 onCancel() {}55 });56 }...
groups.controller.js
Source:groups.controller.js
1const DB = require("../modules/sql.js");2const tvDataController = require("../controllers/tvData.controller.js");3const fallbackController = require("../controllers/fallback.controller.js");4const settingsController = require("../controllers/settings.controller.js");5const UserController = require("../controllers/user.js");6const userController = new UserController();7var Guid = require("guid");8function groupsController() {9 this.createGroupDevice = function() {10 }11 this.getAllGroupsDevices = function() {12 }13 this.getGroupDeviceByGuid = function(params, mask) {14 return Promise.resolve()15 .then(() => {16 return DB.query("SELECT * FROM groups WHERE userId= :userId AND GUID= :guid", params, mask);17 })18 .catch(err => {19 return err;20 });21 }22 this.getSocGroupDeviceByGuid = function(params, mask) {23 return Promise.resolve()24 .then(() => {25 return DB.query("SELECT * FROM groups JOIN groups_desc as gd ON groups.group_id = gd.group_id WHERE GUID = :guid ", params, mask);26 })27 .catch(err => {28 return err;29 });30 }31 this.updateGroupDevice = function() {32 }33 this.deleteGroupDevice = function() {34 }35}...
Using AI Code Generation
1var devicefarmer = require('devicefarmer-stf');2var stf = new devicefarmer();3stf.deleteGroupDevice('groupname', 'deviceid', function(data) {4 console.log(data);5});6var devicefarmer = require('devicefarmer-stf');7var stf = new devicefarmer();8stf.deleteGroup('groupname', function(data) {9 console.log(data);10});11var devicefarmer = require('devicefarmer-stf');12var stf = new devicefarmer();13stf.getGroupDevices('groupname', function(data) {14 console.log(data);15});16var devicefarmer = require('devicefarmer-stf');17var stf = new devicefarmer();18stf.getGroup('groupname', function(data) {19 console.log(data);20});21var devicefarmer = require('devicefarmer-stf');22var stf = new devicefarmer();23stf.getGroups(function(data) {24 console.log(data);25});26var devicefarmer = require('devicefarmer-stf');27var stf = new devicefarmer();28stf.createGroup('groupname', function(data) {29 console.log(data);30});31var devicefarmer = require('devicefarmer-stf');32var stf = new devicefarmer();33stf.getDevice('deviceid', function(data) {34 console.log(data);35});36var devicefarmer = require('devicefarmer-stf');37var stf = new devicefarmer();38stf.getDevices(function(data) {39 console.log(data);40});41var devicefarmer = require('devicefarmer
Using AI Code Generation
1var devicefarmer = require('devicefarmer-stf-client');2client.deleteGroupDevice("Group Name", "Device Serial Number", function(err, data) {3 if (err) {4 console.log(err);5 } else {6 console.log(data);7 }8});9{ success: true }
Using AI Code Generation
1var devicefarmer = require('devicefarmer-stf');2stf.deleteGroupDevice('group name','device serial number');3var devicefarmer = require('devicefarmer-stf');4stf.deleteGroup('group name');5var devicefarmer = require('devicefarmer-stf');6stf.deleteUser('user name');7var devicefarmer = require('devicefarmer-stf');8stf.deleteDevice('device serial number');9var devicefarmer = require('devicefarmer-stf');10stf.deleteDeviceFromPool('device serial number');11var devicefarmer = require('devicefarmer-stf');12stf.deleteDeviceFromPool('device serial number');
Using AI Code Generation
1var Client = require('devicefarmer-stf-client');2client.deleteGroupDevice(1, 2, function(err, res) {3 if (err) {4 console.log(err);5 } else {6 console.log(res);7 }8});9var Client = require('devicefarmer-stf-client');10client.deleteGroup(1, function(err, res) {11 if (err) {12 console.log(err);13 } else {14 console.log(res);15 }16});17var Client = require('devicefarmer-stf-client');18client.getGroup(1, function(err, res) {19 if (err) {20 console.log(err);21 } else {22 console.log(res);23 }24});25var Client = require('devicefarmer-stf-client');26client.getGroupDevices(1, function(err, res) {27 if (err) {28 console.log(err);29 } else {30 console.log(res);31 }32});33var Client = require('devicefarmer-stf-client');34client.getGroups(function(err, res) {35 if (err) {36 console.log(err);37 } else {38 console.log(res);39 }40});41var Client = require('devicefarmer-stf-client');42client.getGroupsDevices(function(err, res) {43 if (err) {44 console.log(err);45 } else {46 console.log(res);47 }48});
Using AI Code Generation
1var stf = require('devicefarmer-stf');2client.deleteGroupDevice("deviceID",function(err, res, body) {3 console.log(body);4});5var stf = require('devicefarmer-stf');6client.deleteGroup("groupID",function(err, res, body) {7 console.log(body);8});9var stf = require('devicefarmer-stf');10client.deleteDevice("deviceID",function(err, res, body) {11 console.log(body);12});13var stf = require('devicefarmer-stf');14client.deleteApp("appID",function(err, res, body) {15 console.log(body);16});17var stf = require('devicefarmer-stf');18client.deleteAccount("accountID",function(err, res, body) {19 console.log(body);20});21var stf = require('devicefarmer-stf');22client.createGroup("groupName",function(err, res, body) {23 console.log(body);24});25var stf = require('devicefarmer-stf');26client.createDevice("deviceName",function(err, res, body) {27 console.log(body);28});29var stf = require('devicefarmer-stf');
Using AI Code Generation
1var DeviceFarmer = require('devicefarmer-stf-client');2client.getGroupDevices(groupId, function(err, devices) {3 if (err) {4 console.log('Error: ' + err);5 } else {6 var deviceId = devices[0].id;7 client.deleteGroupDevice(groupId, deviceId, function(err, data) {8 if (err) {9 console.log('Error: ' + err);10 } else {11 console.log('Device deleted successfully');12 }13 });14 }15});16var DeviceFarmer = require('devicefarmer-stf-client');17client.getGroupDevices(groupId, function(err, devices) {18 if (err) {19 console.log('Error: ' + err);20 } else {21 var deviceId = devices[0].id;22 client.deleteGroupDevice(groupId, deviceId, function(err, data) {23 if (err) {24 console.log('Error: ' + err);25 } else {26 console.log('Device deleted successfully');27 }28 });29 }30});31var DeviceFarmer = require('devicefarmer-stf-client');32client.getGroupDevices(groupId, function(err, devices) {33 if (err) {34 console.log('Error: ' + err);35 } else {36 var deviceId = devices[0].id;37 client.deleteGroupDevice(groupId, deviceId, function(err, data) {38 if (err) {39 console.log('Error: ' + err);40 } else {41 console.log('Device deleted successfully');42 }43 });44 }45});
Using AI Code Generation
1var devicefarmer = require('devicefarmer-stf');2stf.deleteGroupDevice('c7b2f2a2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', 'f5b7c9d2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', function(err, data) {3 if (err) {4 console.log(err);5 } else {6 console.log(data);7 }8});9var devicefarmer = require('devicefarmer-stf');10stf.deleteGroupDevice('c7b2f2a2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', 'f5b7c9d2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', function(err, data) {11 if (err) {12 console.log(err);13 } else {14 console.log(data);15 }16});17var devicefarmer = require('devicefarmer-stf');18stf.deleteGroupDevice('c7b2f2a2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', 'f5b7c9d2-0d9f-4e7c-bb8f-0f1b6d3d0e1a', function(err, data) {19 if (err) {20 console.log(err);21 } else {22 console.log(data);23 }24});
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!