Best Python code snippet using playwright-python
map_editor.py
Source:map_editor.py
1import pygame, os, json, pygame_textinput, startMenu2from map_tile import MapTile3from button import Button4# Load Images5fileDir = os.path.dirname(__file__)6assetDir = os.path.join(fileDir, 'assets')7grass = pygame.image.load(os.path.join(assetDir, "grass.png"))8# Load transparent squares9red = pygame.image.load(os.path.join(assetDir, "red_square.png"))10blue = pygame.image.load(os.path.join(assetDir, "blue_square.png"))11green = pygame.image.load(os.path.join(assetDir, "green_square.png"))12grey = pygame.image.load(os.path.join(assetDir, "grey_square.png"))13# Load Button Images14vpImg = pygame.image.load(os.path.join(assetDir, "viewProperties_button.png"))15spawnImg = pygame.image.load(os.path.join(assetDir, "setSpawn_button.png"))16changeOcImg = pygame.image.load(os.path.join(assetDir, "changeOccupied_button.png"))17jsonImg = pygame.image.load(os.path.join(assetDir, "saveAsJson_button.png"))18saveImg = pygame.image.load(os.path.join(assetDir, "saveAsImg_button.png"))19emptyImg = pygame.image.load(os.path.join(assetDir, "setEmpty_button.png"))20collImg = pygame.image.load(os.path.join(assetDir, "setCollision_button.png"))21# Create GUI Buttons22spawn_Button = Button(spawnImg, 1000, 500, 100, 40)23changeOc_Button = Button(changeOcImg, 1100, 500, 100, 40)24json_Button = Button(jsonImg, 1100, 540, 100, 40)25save_Button = Button(saveImg, 1000, 540, 100, 40)26vp_Button = Button(vpImg, 1000, 580, 100, 40)27empty_Button = Button(emptyImg, 1100, 580, 100, 40)28collision_Button = Button(collImg, 1000, 620, 100, 40)29# Toggle switches30viewToggle = True31spawnToggle = True32occupiedToggle = True33emptyToggle = True34collToggle = True35# Create run window36screen_width, screen_height = 1200, 100037STEP = 10038WIN = pygame.display.set_mode((screen_width, screen_height), pygame.RESIZABLE)39pygame.display.set_caption("Map Editor")40info = pygame.display.Info()41width, height = info.current_w, info.current_h42# Global map variables43global mapX44global mapY45mapX = 046mapY = 047def createMap(name, width, height, terrain, tileImg, window):48 # Create an array of MapTiles that can be accessed anywhere in the file49 global mapArray50 global NAME51 NAME = name52 #print(NAME)53 mapArray = []54 for i in range(0, width, STEP):55 sub = []56 for j in range(0, height, STEP): 57 #if i >= 1000 or j >= 1000:58 # sub.append(MapTile(terrain, (i, j), False, False, False, False))59 #else:60 window.blit(tileImg, (i, j))61 sub.append(MapTile(terrain, (i, j)))62 mapArray.append(sub)63 #print(len(mapArray), len(mapArray[0]))64def loadMap(name, window):65 # Declare Global Vars66 global mapArray67 global NAME68 NAME = name69 70 # Take the name of the map71 fileName = name + ".json"72 filePath = os.path.join(assetDir, fileName)73 # Load map Image74 mapImg = pygame.image.load(os.path.join(assetDir, name + ".png"))75 window.blit(mapImg, (mapX, mapY))76 77 # Open JSON file78 with open(filePath) as inFile:79 mapData = json.load(inFile)80 81 dimensions = mapData["Dimensions"]82 mapArray = []83 for i in range(0, dimensions[0]):84 sub = []85 for j in range(0, dimensions[1]):86 key = i, j87 data = mapData[str(key)]88 sub.append(MapTile(data['terrain'], data['location'], data['isOccupied'], data['isSpawn'], data['isCollision']))89 mapArray.append(sub)90def uniformSize():91 # x, y92 tup = (len(mapArray), len(mapArray[0]))93 return tup94def moveTools(button):95 size = uniformSize()96 width = size[0] * 10097 height = size[1] * 10098 button.setX(width)99 button.setY(height)100def moveSecondaryTools(buttonA, buttonB):101 # Move buttonA alongside buttonB102 dim = buttonB.getDimensions()103 loc = buttonB.getLocation()104 newX = loc[0] + dim[0]105 newY = loc[1] + dim[1]106 buttonA.setX(newX)107 buttonA.setY(newY)108def getTileLoc():109 # Gets the tile that the mouse is hovering over110 size = uniformSize()111 #width, height = pygame.display.get_surface().get_size()112 for i in range(size[0]):113 for j in range(size[1]):114 if mapArray[i][j].isMouseOver():115 return mapArray[i][j].getLocation()116 117# The Following functions are called on mouse click, Both functions are for drawing tiles(terrains)118# drawTile draws overtop of the previous tile, but does not update the MapTile119# changeTile changes the terrain of the MapTile, and then calls the MapTile draw function to replace the previous tile120def drawTile(tileImg, window):121 # Grabs the location of MapTile, then draws over top122 location = getTileLoc()123 window.blit(tileImg, (location))124 pygame.display.update()125 126def changeTile (terrain, window):127 # grab the location of MapTile, then change the terrain and the call the class draw func128 location = getTileLoc()129 # //10 will give the location of the MapTile in the 2d array130 x, y = location[0]//STEP, location[1]//STEP131 mapArray[x][y].setTerrain(terrain)132 mapArray[x][y].drawMapTile(window)133def setSpawn():134 # change the spawn property of the maptile135 pos = pygame.mouse.get_pos()136 size = uniformSize()137 if spawnToggle == False:138 if pos[0] >= 0 and pos[0] < (1000):139 if pos[1] >= 0 and pos[1] < (1000):140 if event.type == pygame.MOUSEBUTTONDOWN:141 loc = getTileLoc()142 mapArray[loc[0]//STEP][loc[1]//STEP].setSpawn(True)143 144def setOccupied():145 pos = pygame.mouse.get_pos()146 size = uniformSize()147 if occupiedToggle == False:148 if pos[0] >= 0 and pos[0] < (1000):149 if pos[1] >= 0 and pos[1] < (1000):150 if event.type == pygame.MOUSEBUTTONDOWN:151 loc = getTileLoc()152 mapArray[loc[0]//STEP][loc[1]//STEP].setOccupied(True)153def setCollision():154 pos = pygame.mouse.get_pos()155 size = uniformSize()156 if collToggle == False:157 if pos[0] >= 0 and pos[0] < (1000):158 if pos[1] >= 0 and pos[1] < (1000):159 if event.type == pygame.MOUSEBUTTONDOWN:160 loc = getTileLoc()161 mapArray[loc[0]//STEP][loc[1]//STEP].setCollision(True)162def setEmpty():163 pos = pygame.mouse.get_pos()164 size = uniformSize()165 if emptyToggle == False:166 if pos[0] >= 0 and pos[0] < (1000):167 if pos[1] >= 0 and pos[1] < (1000):168 if event.type == pygame.MOUSEBUTTONDOWN:169 loc = getTileLoc()170 mapArray[loc[0]//STEP][loc[1]//STEP].setEmpty()171def viewProperties(window):172 # If viewing is false then not viewing properties, set to true and view properties173 # If viewing is true, then viewing properties, set to false and stop viewing properties174 size = uniformSize()175 if viewToggle == False:176 #width, height = pygame.display.get_surface().get_size()177 for i in range(size[0]):178 for j in range(size[1]):179 # x, y = i//STEP, j//STEP180 if mapArray[i][j].getCollision():181 # If the tile is a collision tile - set to grey182 loc = mapArray[i][j].getLocation()183 window.blit(grey, (loc))184 if mapArray[i][j].getSpawn():185 # If the tile is a spawn tile - set to green186 loc = mapArray[i][j].getLocation()187 window.blit(green, (loc))188 if mapArray[i][j].getOccupied():189 # If the tile is occupied - set to red190 loc = mapArray[i][j].getLocation()191 window.blit(red, (loc))192 if mapArray[i][j].getEmpty():193 # If set to neither - set to blue194 loc = mapArray[i][j].getLocation()195 window.blit(blue, (loc))196 pygame.display.update()197def saveJson():198 size = uniformSize()199 fileName = NAME + ".json"200 stream = os.path.join(assetDir, fileName)201 data = {}202 data["Dimensions"] = (size[0], size[1])203 #data["Dimensions"].append((str(size[0]), str(size[1])))204 for i in range(size[0]):205 for j in range(size[1]):206 # x, y = i//STEP, j//STEP207 #info = mapArray[i][j].getLocation()208 #key = info[0]//STEP, info[1]//STEP209 #data[str(key)] = (mapArray[i][j].getInfo())210 #data[str(key)].append(str(mapArray[i][j]))211 212 tup = i, j213 data[str(tup)] = mapArray[i][j].saveDict()214 215 with open(stream, 'w') as outFile:216 json.dump(data, outFile)217def savePNG(window):218 fileName = NAME + ".png"219 stream = os.path.join(assetDir, fileName)220 size = uniformSize()221 rect = pygame.Rect(0,0,size[0] * STEP, size[1] * STEP)222 sub = window.subsurface(rect)223 pygame.image.save(sub, stream)224# Create the map225if startMenu.loadToggle:226 loadMap(startMenu.name, WIN)227else:228 createMap(startMenu.name,startMenu.width,startMenu.height,"grass",grass,WIN) 229def moveMap(direction, distance):230 # Counters for each direction Up/Down(-/+), Right/Left(-/+)231 size = uniformSize()232 # e.g. size[0] = 12, 1000//STEP = 10, therefore scroll up to 2 spaces233 #maxDist = size[0] - 10, size[1] - 10 # (x, y)234 235 if direction == "Up": # Scroll Up236 for i in range(size[0]):237 for j in range(size[1]):238 mapArray[i][j].move(100, "Up")239 if direction == "Down": # Scroll Down 240 for i in range(size[0]):241 for j in range(size[1]):242 mapArray[i][j].move(100, "Down")243 if direction == "Right": # Scroll Right244 for i in range(size[0]):245 for j in range(size[1]):246 mapArray[i][j].move(100, "Right")247 if direction == "Left": # Scroll Left248 for i in range(size[0]):249 for j in range(size[1]):250 mapArray[i][j].move(100, "Left")251 252 # Check if maptile is offscreen253 '''254 for i in range(size[0]):255 for j in range(size[1]):256 if i * STEP >= 1000 or j * STEP >= 1000:257 mapArray[i][j].setVisible(False)258 else:259 mapArray[i][j].setVisible(True)260 '''261def redraw():262 263 size = uniformSize()264 for i in range(size[0]):265 for j in range(size[1]):266 mapArray[i][j].drawMapTile(WIN)267 # Draw properties268 viewProperties(WIN)269 # Draw Toolbar Rectangle270 toolbar = pygame.Rect(1000, 0, 1000, 1200)271 pygame.draw.rect(WIN, (0,0,0), toolbar)272 # Draw Buttons273 274 vp_Button.drawButton(WIN)275 spawn_Button.drawButton(WIN)276 json_Button.drawButton(WIN)277 save_Button.drawButton(WIN)278 changeOc_Button.drawButton(WIN)279 empty_Button.drawButton(WIN)280 collision_Button.drawButton(WIN)281 # Update 282 pygame.display.update()283# Run Loop284running = True285size = uniformSize()286maxDist = size[0] - 10, size[1] - 10 # (x, y)287mapX, mapY = 0, 0288while running:289 redraw()290 keys = pygame.key.get_pressed()291 for event in pygame.event.get():292 if event.type == pygame.QUIT:293 running = False294 pygame.quit()295 quit()296 if event.type == pygame.VIDEORESIZE:297 WIN = pygame.display.set_mode((event.w, event.h), pygame.RESIZABLE)298 299 # If Buttons are clicked300 if event.type == pygame.MOUSEBUTTONDOWN:301 if vp_Button.mouseOver(): # View Properties Button302 viewToggle = not viewToggle303 if spawn_Button.mouseOver(): # Spawn Button304 spawnToggle = not spawnToggle305 occupiedToggle = True306 emptyToggle = True307 collToggle = True308 if changeOc_Button.mouseOver(): # Change Occupied Button309 occupiedToggle = not occupiedToggle310 spawnToggle = True311 emptyToggle = True312 collToggle = True313 if collision_Button.mouseOver(): # Set Collision Button314 collToggle = not collToggle315 spawnToggle = True316 occupiedToggle = True317 emptyToggle = True318 if empty_Button.mouseOver(): # Set Empty Button319 emptyToggle = not emptyToggle320 spawnToggle = True321 occupiedToggle = True322 collToggle = True323 if json_Button.mouseOver():324 saveJson()325 if save_Button.mouseOver():326 savePNG(WIN)327 328 if keys[pygame.K_UP] and mapY > -maxDist[1]:329 mapY -= 1330 moveMap("Up", 100)331 332 if keys[pygame.K_DOWN] and mapY < 0:333 mapY += 1334 moveMap("Down", 100)335 336 if keys[pygame.K_RIGHT] and mapX > -maxDist[0]:337 mapX -= 1338 moveMap("Right", 100)339 340 if keys[pygame.K_LEFT] and mapX < 0:341 mapX += 1342 moveMap("Left", 100)343 344 # Run Tools345 setSpawn()346 setOccupied()347 setCollision()348 setEmpty()349 # Update Display350 pygame.display.update()...
asset_opener.py
Source:asset_opener.py
1import os, sys2from guerilla import Document3from PySide import QtGui4from variables import *5class assetOpener(QtGui.QWidget):6 def __init__(self):7 super(assetOpener, self).__init__()8 self.setWindowTitle('Liquerilla Opener')9 QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))10 self.resize(200,200)11 # scroll buttons12 self.assSeq = QtGui.QComboBox(self)13 self.typeBox = QtGui.QComboBox(self)14 self.assetBox = QtGui.QComboBox(self)15 self.versionBox = QtGui.QComboBox(self)16 self.openButton = QtGui.QPushButton('Open Scene File !')17 self.folderButton = QtGui.QPushButton('The Mighty Folder Opener')18 # ...in a vertical layout19 vbox = QtGui.QVBoxLayout()20 vbox.addWidget(self.assSeq)21 vbox.addWidget(self.typeBox)22 vbox.addWidget(self.assetBox)23 vbox.addWidget(self.versionBox)24 vbox.addWidget(self.openButton)25 vbox.addWidget(self.folderButton)26 # set our vertical layout27 self.setLayout(vbox)28 self.fillAssSeq()29 self.fillTypes()30 self.fillAssets()31 self.fillVersions()32 # connect signals33 self.assSeq.currentIndexChanged.connect(self.fillTypes)34 self.typeBox.currentIndexChanged.connect(self.fillAssets)35 self.assetBox.currentIndexChanged.connect(self.fillVersions)36 self.openButton.clicked.connect(self.openAsset)37 self.folderButton.clicked.connect(self.openFolder)38 def fillAssSeq(self):39 self.assSeq.clear()40 dirs = ['assets', 'seq']41 for each in dirs:42 self.assSeq.addItem(each)43 def fillTypes(self):44 self.typeBox.clear()45 assSeq = self.assSeq.currentText()46 dirs = os.listdir(project_dir + '/prod/' + assSeq)47 for each in dirs:48 self.typeBox.addItem(each)49 def fillAssets(self):50 self.assetBox.clear()51 assSeq = self.assSeq.currentText()52 assetDir = project_dir + '/prod/' + assSeq53 assetType = self.typeBox.currentText()54 if assSeq == 'assets':55 dirs = os.listdir(assetDir + '/' + assetType)56 else:57 dirs = os.listdir(assetDir + '/' + assetType + '/shots')58 self.assetBox.addItem('lookdev')59 for each in dirs:60 self.assetBox.addItem(each)61 def fillVersions(self):62 self.versionBox.clear()63 assSeq = self.assSeq.currentText()64 assetDir = project_dir + '/prod/' + assSeq65 assetType = self.typeBox.currentText()66 assetName = self.assetBox.currentText()67 if assSeq == 'assets':68 dirs = os.listdir(assetDir + '/' + assetType + '/' + assetName + '/lookdev/guerilla')69 else:70 if assetName == 'lookdev':71 dirs = os.listdir(assetDir + '/' + assetType + '/' + assetName + '/guerilla')72 else:73 dirs = os.listdir(assetDir + '/' + assetType + '/shots/' + assetName + '/lookdev/guerilla')74 if not dirs == []:75 for each in dirs:76 self.versionBox.addItem(each)77 def printVer(self):78 assetIndex = self.versionBox.currentIndex()79 assetType = self.versionBox.currentText()80 def openAsset(self):81 assSeq = str(self.assSeq.currentText())82 assetDir = project_dir + '/prod/' + assSeq83 assetType = str(self.typeBox.currentText())84 assetName = str(self.assetBox.currentText())85 assetVersion= str(self.versionBox.currentText())86 if assSeq == 'assets':87 asset_file = assetDir + '/' + assetType + '/' + assetName + '/lookdev/guerilla/' + assetVersion + '/lookdev.gproject'88 elif assetName == 'lookdev':89 asset_file = assetDir + '/' + assetType + '/' + assetName + '/guerilla/' + assetVersion + '/lookdev.gproject'90 else:91 asset_file = assetDir + '/' + assetType + '/shots/' + assetName + '/lookdev/guerilla/' + assetVersion + '/lookdev.gproject'92 d = Document()93 d.load(asset_file)94 def openFolder(self):95 import subprocess96 doc = Document()97 file_name = doc.getfilename()98 charvalue = str(file_name).split('/')99 charvalue.pop(-1)100 charvalue.pop(-1)101 charvalue.pop(-1)102 file_name = '\\'.join(charvalue)103 subprocess.call('explorer ' + file_name, shell=True)104app = QtGui.QApplication.instance()105if app is None:106 app = QtGui.QApplication(sys.argv)107aO = assetOpener()...
gen_config.py
Source:gen_config.py
1#!/usr/bin/env python32#-*- coding: utf-8 -*-3from genericpath import isfile4import json5import os6from posix import listdir7import sys8import tarfile9import subprocess10import time11import traceback12list = {}13games = [f for f in os.listdir("./games/") if os.path.isdir("./games/"+f)]14oldAssets = {}15with open("assets.json", 'r', encoding='utf-8') as oldAssetsFile:16 oldAssets = json.load(oldAssetsFile)17lastVersions = {}18# with open("last_versions.json", 'r', encoding='utf-8') as lastVersionsFile:19# lastVersions = json.load(lastVersionsFile)20for game in games:21 print("Game: "+game)22 path = "./games/"+game+"/"23 try:24 with open(path+"/base_files/config.json", 'r', encoding='utf-8') as configFile:25 config = json.load(configFile)26 list[game] = {27 "name": config["name"],28 "assets": {}29 }30 except Exception as e:31 print(e)32 continue33 34 assetDirs = [f for f in os.listdir(path) if os.path.isdir(path+f)]35 print("Assets: "+str(assetDirs))36 for assetDir in assetDirs:37 assetPath = "./games/"+game+"/"+assetDir+"/"38 modified = True if float(config.get("version", 0)) > float(lastVersions.get(f'{game}.{assetDir}', 0)) else False39 lastVersions[f'{game}.{assetDir}'] = config.get("version", 0)40 if modified:41 deleteOldZips = subprocess.Popen(42 ["rm "+path+"*"+assetDir+".7z*"],43 shell=True44 )45 deleteOldZips.communicate()46 print(">"+assetPath)47 print("Was modified", modified)48 try:49 with open(assetPath+"config.json", 'r', encoding='utf-8') as configFile:50 config = json.load(configFile)51 52 files = {}53 if modified:54 _zip = subprocess.Popen([55 "7z", "-r", "a",56 "./games/"+game+"/"+game+"."+assetDir+".7z",57 "./games/"+game+"/"+assetDir58 ])59 result = _zip.communicate()60 fileNames = [f for f in os.listdir("./games/"+game+"/") if f.startswith(game+"."+assetDir+".7z")]61 for f in fileNames:62 files[f] = {63 "name": f,64 "size": os.path.getsize("./games/"+game+"/"+f)65 }66 else:67 files = oldAssets[game]["assets"][assetDir]["files"]68 list[game]["assets"][assetDir] = {69 "name": config.get("name"),70 "credits": config.get("credits"),71 "description": config.get("description"),72 "files": files,73 "version": config.get("version"),74 "has_stage_data": len(config.get("stage_to_codename", {})) > 0,75 "has_eyesight_data": len(config.get("eyesights", {})) > 076 }77 with open(assetPath+"README.md", 'w', encoding='utf-8') as readme:78 readme.write("# "+config.get("name", "")+"\n\n")79 readme.write("## Description: \n\n"+config.get("description", "")+"\n\n")80 readme.write("## Credits: \n\n"+config.get("credits", "")+"\n\n")81 except Exception as e:82 print(traceback.format_exc())83with open('assets.json', 'w') as outfile:84 json.dump(list, outfile, indent=4, sort_keys=True)85with open('last_versions.json', 'w') as outfile:...
nuke_opener.py
Source:nuke_opener.py
1import os, sys2import nuke3from PySide import QtGui, QtCore4from variables import *5class assetOpener(QtGui.QWidget):6 def __init__(self):7 super(assetOpener, self).__init__()8 self.setWindowTitle('LiqueNuke Opener')9 QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))10 self.resize(250,200)11 # scroll buttons12 self.assSeq = QtGui.QComboBox(self)13 self.typeBox = QtGui.QComboBox(self)14 self.assetBox = QtGui.QComboBox(self)15 self.versionBox = QtGui.QComboBox(self)16 self.openButton = QtGui.QPushButton('Open Nuke Script !')17 # ...in a vertical layout18 vbox = QtGui.QVBoxLayout()19 vbox.addWidget(self.assSeq)20 vbox.addWidget(self.typeBox)21 vbox.addWidget(self.assetBox)22 vbox.addWidget(self.versionBox)23 vbox.addWidget(self.openButton)24 # set our vertical layout25 self.setLayout(vbox)26 self.fillAssSeq()27 self.fillTypes()28 self.fillAssets()29 self.fillVersions()30 # connect signals31 self.assSeq.currentIndexChanged.connect(self.fillTypes)32 self.typeBox.currentIndexChanged.connect(self.fillAssets)33 self.assetBox.currentIndexChanged.connect(self.fillVersions)34 self.openButton.clicked.connect(self.openAsset)35 def fillAssSeq(self):36 self.assSeq.clear()37 dirs = ['assets', 'seq']38 for each in dirs:39 self.assSeq.addItem(each)40 def fillTypes(self):41 self.typeBox.clear()42 assSeq = self.assSeq.currentText()43 dirs = os.listdir(project_dir + '/post/compo/'+ assSeq)44 if 'compo_out' in dirs:45 dirs.remove('compo_out')46 for each in dirs:47 self.typeBox.addItem(each)48 def fillAssets(self):49 self.assetBox.clear()50 assSeq = self.assSeq.currentText()51 assetDir = project_dir + '/post/compo/' + assSeq52 assetType = self.typeBox.currentText()53 dirs = os.listdir(assetDir + '/' + assetType)54 if 'compo_out' in dirs:55 dirs.remove('compo_out')56 for each in dirs:57 self.assetBox.addItem(each)58 def fillVersions(self):59 self.versionBox.clear()60 assSeq = self.assSeq.currentText()61 assetDir = project_dir + '/post/compo/' + assSeq62 assetType = self.typeBox.currentText()63 assetName = self.assetBox.currentText()64 dirs = os.listdir(assetDir + '/' + assetType + '/' + assetName)65 if 'compo_out' in dirs:66 dirs.remove('compo_out')67 if not dirs == []:68 for each in dirs:69 self.versionBox.addItem(each)70 def printVer(self):71 assetIndex = self.versionBox.currentIndex()72 assetType = self.versionBox.currentText()73 def openAsset(self):74 assSeq = self.assSeq.currentText()75 assetDir = project_dir + '/post/compo/' + assSeq76 assetType = str(self.typeBox.currentText())77 assetName = str(self.assetBox.currentText())78 assetVersion= str(self.versionBox.currentText())79 if not assetVersion:80 asset_file = assetDir + '/' + assetType + '/' + assetName81 else:82 asset_file = assetDir + '/' + assetType + '/' + assetName + '/' + assetVersion83 print(asset_file)84 nuke.scriptOpen(asset_file)85def main():86 87 aO = assetOpener()88 aO.show()89 app = QtGui.QApplication.instance(aO)90 if app is None:91 app = QtGui.QApplication(sys.argv)92 sys.exit(app.exec_())93if __name__ == '__main__':...
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!