How to use isDraining method of org.openqa.selenium.grid.node.Node class

Best Selenium code snippet using org.openqa.selenium.grid.node.Node.isDraining

Source:SauceNode.java Github

copy

Full Screen

...130 this.factories = ImmutableList.copyOf(factories);131 Require.nonNull("Registration secret", registrationSecret);132 this.healthCheck = healthCheck == null ?133 () -> new HealthCheck.Result(134 isDraining() ? DRAINING : UP,135 String.format("%s is %s", uri, isDraining() ? "draining" : "up")) :136 healthCheck;137 this.currentSessions = CacheBuilder.newBuilder()138 .expireAfterAccess(sessionTimeout)139 .ticker(ticker)140 .removalListener((RemovalListener<SessionId, SessionSlot>) notification -> {141 // Attempt to stop the session142 LOG.log(Debug.getDebugLogLevel(), "Stopping session {0}", notification.getKey().toString());143 SessionSlot slot = notification.getValue();144 if (!slot.isAvailable()) {145 slot.stop();146 }147 })148 .build();149 this.tempFileSystems = CacheBuilder.newBuilder()150 .expireAfterAccess(sessionTimeout)151 .ticker(ticker)152 .removalListener((RemovalListener<SessionId, TemporaryFilesystem>) notification -> {153 TemporaryFilesystem tempFS = notification.getValue();154 tempFS.deleteTemporaryFiles();155 tempFS.deleteBaseDir();156 })157 .build();158 Regularly sessionCleanup = new Regularly("Session Cleanup Node: " + externalUri);159 sessionCleanup.submit(currentSessions::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));160 Regularly tmpFileCleanup = new Regularly("TempFile Cleanup Node: " + externalUri);161 tmpFileCleanup.submit(tempFileSystems::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));162 Regularly regularHeartBeat = new Regularly("Heartbeat Node: " + externalUri);163 regularHeartBeat.submit(() -> bus.fire(new NodeHeartBeatEvent(getStatus())), heartbeatPeriod,164 heartbeatPeriod);165 bus.addListener(SessionClosedEvent.listener(id -> {166 // Listen to session terminated events so we know when to fire the NodeDrainComplete event167 if (this.isDraining()) {168 int done = pendingSessions.decrementAndGet();169 if (done <= 0) {170 LOG.info("Firing node drain complete message");171 bus.fire(new NodeDrainComplete(this.getId()));172 }173 }174 }));175 Runtime.getRuntime().addShutdownHook(new Thread(this::stopAllSessions));176 new JMXHelper().register(this);177 }178 public static SauceNode.Builder builder(179 Tracer tracer,180 EventBus bus,181 URI uri,182 URI gridUri,183 Secret registrationSecret) {184 return new SauceNode.Builder(tracer, bus, uri, gridUri, registrationSecret);185 }186 @Override187 public boolean isReady() {188 return bus.isReady();189 }190 @VisibleForTesting191 public int getCurrentSessionCount() {192 // It seems wildly unlikely we'll overflow an int193 return Math.toIntExact(currentSessions.size());194 }195 @ManagedAttribute(name = "MaxSessions")196 public int getMaxSessionCount() {197 return maxSessionCount;198 }199 @ManagedAttribute(name = "Status")200 public Availability getAvailability() {201 return isDraining() ? DRAINING : UP;202 }203 @ManagedAttribute(name = "TotalSlots")204 public int getTotalSlots() {205 return factories.size();206 }207 @ManagedAttribute(name = "UsedSlots")208 public long getUsedSlots() {209 return factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();210 }211 @ManagedAttribute(name = "Load")212 public float getLoad() {213 long inUse = factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();214 return inUse / (float) maxSessionCount * 100f;215 }216 @ManagedAttribute(name = "RemoteNodeUri")217 public URI getExternalUri() {218 return this.getUri();219 }220 @ManagedAttribute(name = "GridUri")221 public URI getGridUri() {222 return this.gridUri;223 }224 @ManagedAttribute(name = "NodeId")225 public String getNodeId() {226 return getId().toString();227 }228 @Override229 public boolean isSupporting(Capabilities capabilities) {230 return factories.parallelStream().anyMatch(factory -> factory.test(capabilities));231 }232 @Override233 public Either<WebDriverException, CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {234 Require.nonNull("Session request", sessionRequest);235 try (Span span = tracer.getCurrentContext().createSpan("node.new_session")) {236 Map<String, EventAttributeValue> attributeMap = new HashMap<>();237 attributeMap238 .put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName()));239 LOG.fine("Creating new session using span: " + span);240 attributeMap.put("session.request.capabilities",241 EventAttribute.setValue(sessionRequest.getDesiredCapabilities().toString()));242 attributeMap.put("session.request.downstreamdialect",243 EventAttribute.setValue(sessionRequest.getDownstreamDialects().toString()));244 int currentSessionCount = getCurrentSessionCount();245 span.setAttribute("current.session.count", currentSessionCount);246 attributeMap.put("current.session.count", EventAttribute.setValue(currentSessionCount));247 if (getCurrentSessionCount() >= maxSessionCount) {248 span.setAttribute("error", true);249 span.setStatus(Status.RESOURCE_EXHAUSTED);250 attributeMap.put("max.session.count", EventAttribute.setValue(maxSessionCount));251 span.addEvent("Max session count reached", attributeMap);252 return Either.left(new RetrySessionRequestException("Max session count reached."));253 }254 if (isDraining()) {255 span.setStatus(Status.UNAVAILABLE.withDescription("The node is draining. Cannot accept new sessions."));256 return Either.left(257 new RetrySessionRequestException("The node is draining. Cannot accept new sessions."));258 }259 // Identify possible slots to use as quickly as possible to enable concurrent session starting260 SessionSlot slotToUse = null;261 synchronized(factories) {262 for (SessionSlot factory : factories) {263 if (!factory.isAvailable() || !factory.test(sessionRequest.getDesiredCapabilities())) {264 continue;265 }266 factory.reserve();267 slotToUse = factory;268 break;269 }270 }271 if (slotToUse == null) {272 span.setAttribute("error", true);273 span.setStatus(Status.NOT_FOUND);274 span.addEvent("No slot matched capabilities ", attributeMap);275 return Either.left(276 new RetrySessionRequestException("No slot matched the requested capabilities."));277 }278 Either<WebDriverException, ActiveSession> possibleSession = slotToUse.apply(sessionRequest);279 if (possibleSession.isRight()) {280 ActiveSession session = possibleSession.right();281 currentSessions.put(session.getId(), slotToUse);282 SessionId sessionId = session.getId();283 Capabilities caps = session.getCapabilities();284 SESSION_ID.accept(span, sessionId);285 CAPABILITIES.accept(span, caps);286 String downstream = session.getDownstreamDialect().toString();287 String upstream = session.getUpstreamDialect().toString();288 String sessionUri = session.getUri().toString();289 span.setAttribute(AttributeKey.DOWNSTREAM_DIALECT.getKey(), downstream);290 span.setAttribute(AttributeKey.UPSTREAM_DIALECT.getKey(), upstream);291 span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri);292 // The session we return has to look like it came from the node, since we might be dealing293 // with a webdriver implementation that only accepts connections from localhost294 boolean isSupportingCdp = slotToUse.isSupportingCdp() ||295 caps.getCapability("se:cdp") != null;296 Session externalSession = createExternalSession(session, externalUri, isSupportingCdp);297 return Either.right(new CreateSessionResponse(298 externalSession,299 getEncoder(session.getDownstreamDialect()).apply(externalSession)));300 } else {301 slotToUse.release();302 span.setAttribute("error", true);303 span.addEvent("Unable to create session with the driver", attributeMap);304 return Either.left(possibleSession.left());305 }306 }307 }308 @Override309 public boolean isSessionOwner(SessionId id) {310 Require.nonNull("Session ID", id);311 return currentSessions.getIfPresent(id) != null;312 }313 @Override314 public Session getSession(SessionId id) throws NoSuchSessionException {315 Require.nonNull("Session ID", id);316 SessionSlot slot = currentSessions.getIfPresent(id);317 if (slot == null) {318 throw new NoSuchSessionException("Cannot find session with id: " + id);319 }320 return createExternalSession(slot.getSession(), externalUri, slot.isSupportingCdp());321 }322 @Override323 public TemporaryFilesystem getTemporaryFilesystem(SessionId id) throws IOException {324 try {325 return tempFileSystems.get(id, () -> TemporaryFilesystem.getTmpFsBasedOn(326 TemporaryFilesystem.getDefaultTmpFS().createTempDir("session", id.toString())));327 } catch (ExecutionException e) {328 throw new IOException(e);329 }330 }331 @Override332 public HttpResponse executeWebDriverCommand(HttpRequest req) {333 // True enough to be good enough334 SessionId id = getSessionId(req.getUri()).map(SessionId::new)335 .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req));336 SessionSlot slot = currentSessions.getIfPresent(id);337 if (slot == null) {338 throw new NoSuchSessionException("Cannot find session with id: " + id);339 }340 ActiveSession activeSession = slot.getSession();341 if (activeSession.getClass().getName().contains("RelaySessionFactory")) {342 HttpResponse toReturn = slot.execute(req);343 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {344 stop(id);345 }346 return toReturn;347 }348 SauceDockerSession session = (SauceDockerSession) activeSession;349 SauceCommandInfo.Builder builder = new SauceCommandInfo.Builder();350 builder.setStartTime(Instant.now().toEpochMilli());351 HttpResponse toReturn = slot.execute(req);352 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {353 stop(id);354 builder.setScreenshotId(-1);355 } else {356 // Only taking screenshots after a url has been loaded357 if (!session.canTakeScreenshot() && req.getMethod() == POST358 && req.getUri().endsWith("/url")) {359 session.enableScreenshots();360 }361 int screenshotId = takeScreenshot(session, req, slot);362 builder.setScreenshotId(screenshotId);363 }364 Map<String, Object> parsedResponse =365 JSON.toType(new InputStreamReader(toReturn.getContent().get()), MAP_TYPE);366 builder.setRequest(getRequestContents(req))367 .setResult(parsedResponse)368 .setPath(req.getUri().replace(String.format("/session/%s", id), ""))369 .setHttpStatus(toReturn.getStatus())370 .setHttpMethod(req.getMethod().name())371 .setStatusCode(0);372 if (parsedResponse.containsKey("value") && parsedResponse.get("value") != null373 && parsedResponse.get("value").toString().contains("error")) {374 builder.setStatusCode(1);375 }376 builder.setEndTime(Instant.now().toEpochMilli());377 session.addSauceCommandInfo(builder.build());378 return toReturn;379 }380 @Override381 public HttpResponse uploadFile(HttpRequest req, SessionId id) {382 // When the session is running in a Docker container, the upload file command383 // needs to be forwarded to the container as well.384 SessionSlot slot = currentSessions.getIfPresent(id);385 if (slot != null && slot.getSession() instanceof SauceDockerSession) {386 return executeWebDriverCommand(req);387 }388 Map<String, Object> incoming = JSON.toType(string(req), Json.MAP_TYPE);389 File tempDir;390 try {391 TemporaryFilesystem tempfs = getTemporaryFilesystem(id);392 tempDir = tempfs.createTempDir("upload", "file");393 Zip.unzip((String) incoming.get("file"), tempDir);394 } catch (IOException e) {395 throw new UncheckedIOException(e);396 }397 // Select the first file398 File[] allFiles = tempDir.listFiles();399 if (allFiles == null) {400 throw new WebDriverException(401 String.format("Cannot access temporary directory for uploaded files %s", tempDir));402 }403 if (allFiles.length != 1) {404 throw new WebDriverException(405 String.format("Expected there to be only 1 file. There were: %s", allFiles.length));406 }407 ImmutableMap<String, Object> result = ImmutableMap.of(408 "value", allFiles[0].getAbsolutePath());409 return new HttpResponse().setContent(asJson(result));410 }411 @Override412 public void stop(SessionId id) throws NoSuchSessionException {413 Require.nonNull("Session ID", id);414 SessionSlot slot = currentSessions.getIfPresent(id);415 if (slot == null) {416 throw new NoSuchSessionException("Cannot find session with id: " + id);417 }418 currentSessions.invalidate(id);419 tempFileSystems.invalidate(id);420 }421 private void stopAllSessions() {422 if (currentSessions.size() > 0) {423 LOG.info("Trying to stop all running sessions before shutting down...");424 currentSessions.invalidateAll();425 }426 }427 private Session createExternalSession(ActiveSession other, URI externalUri, boolean isSupportingCdp) {428 Capabilities toUse = ImmutableCapabilities.copyOf(other.getCapabilities());429 // Rewrite the se:options if necessary to send the cdp url back430 if (isSupportingCdp) {431 String cdpPath = String.format("/session/%s/se/cdp", other.getId());432 toUse = new PersistentCapabilities(toUse).setCapability("se:cdp", rewrite(cdpPath));433 }434 return new Session(other.getId(), externalUri, other.getStereotype(), toUse, Instant.now());435 }436 private URI rewrite(String path) {437 try {438 String scheme = "https".equals(gridUri.getScheme()) ? "wss" : "ws";439 return new URI(440 scheme,441 gridUri.getUserInfo(),442 gridUri.getHost(),443 gridUri.getPort(),444 path,445 null,446 null);447 } catch (URISyntaxException e) {448 throw new RuntimeException(e);449 }450 }451 @Override452 public NodeStatus getStatus() {453 Set<Slot> slots = factories.stream()454 .map(slot -> {455 Instant lastStarted = Instant.EPOCH;456 Session session = null;457 if (!slot.isAvailable()) {458 ActiveSession activeSession = slot.getSession();459 if (activeSession != null) {460 lastStarted = activeSession.getStartTime();461 session = new Session(462 activeSession.getId(),463 activeSession.getUri(),464 slot.getStereotype(),465 activeSession.getCapabilities(),466 activeSession.getStartTime());467 }468 }469 return new Slot(470 new SlotId(getId(), slot.getId()),471 slot.getStereotype(),472 lastStarted,473 session);474 })475 .collect(toImmutableSet());476 return new NodeStatus(477 getId(),478 externalUri,479 maxSessionCount,480 slots,481 isDraining() ? DRAINING : UP,482 heartbeatPeriod,483 getNodeVersion(),484 getOsInfo());485 }486 @Override487 public HealthCheck getHealthCheck() {488 return healthCheck;489 }490 @Override491 public void drain() {492 bus.fire(new NodeDrainStarted(getId()));493 draining = true;494 int currentSessionCount = getCurrentSessionCount();495 if (currentSessionCount == 0) {496 LOG.info("Firing node drain complete message");497 bus.fire(new NodeDrainComplete(getId()));498 } else {499 pendingSessions.set(currentSessionCount);500 }501 }502 private Map<String, Object> toJson() {503 return ImmutableMap.of(504 "id", getId(),505 "uri", externalUri,506 "maxSessions", maxSessionCount,507 "draining", isDraining(),508 "capabilities", factories.stream()509 .map(SessionSlot::getStereotype)510 .collect(Collectors.toSet()));511 }512 public static class Builder {513 private final Tracer tracer;514 private final EventBus bus;515 private final URI uri;516 private final URI gridUri;517 private final Secret registrationSecret;518 private final ImmutableList.Builder<SessionSlot> factories;519 private int maxCount = Runtime.getRuntime().availableProcessors() * 5;520 private Ticker ticker = Ticker.systemTicker();521 private Duration sessionTimeout = Duration.ofMinutes(5);...

Full Screen

Full Screen

Source:LocalNode.java Github

copy

Full Screen

...133 this.factories = ImmutableList.copyOf(factories);134 Require.nonNull("Registration secret", registrationSecret);135 this.healthCheck = healthCheck == null ?136 () -> new HealthCheck.Result(137 isDraining() ? DRAINING : UP,138 String.format("%s is %s", uri, isDraining() ? "draining" : "up")) :139 healthCheck;140 this.currentSessions = CacheBuilder.newBuilder()141 .expireAfterAccess(sessionTimeout)142 .ticker(ticker)143 .removalListener((RemovalListener<SessionId, SessionSlot>) notification -> {144 // If we were invoked explicitly, then return: we know what we're doing.145 if (!notification.wasEvicted()) {146 return;147 }148 killSession(notification.getValue());149 })150 .build();151 this.tempFileSystems = CacheBuilder.newBuilder()152 .expireAfterAccess(sessionTimeout)153 .ticker(ticker)154 .removalListener((RemovalListener<SessionId, TemporaryFilesystem>) notification -> {155 TemporaryFilesystem tempFS = notification.getValue();156 tempFS.deleteTemporaryFiles();157 tempFS.deleteBaseDir();158 })159 .build();160 this.regularly = new Regularly("Local Node: " + externalUri);161 regularly.submit(currentSessions::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));162 regularly.submit(tempFileSystems::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));163 bus.addListener(NodeAddedEvent.listener(nodeId -> {164 if (getId().equals(nodeId)) {165 // Lets avoid to create more than one "Regularly" when the Node registers again.166 if (!heartBeatStarted.getAndSet(true)) {167 regularly.submit(168 () -> bus.fire(new NodeHeartBeatEvent(getStatus())), heartbeatPeriod, heartbeatPeriod);169 }170 }171 }));172 bus.addListener(SessionClosedEvent.listener(id -> {173 try {174 this.stop(id);175 } catch (NoSuchSessionException ignore) {176 }177 if (this.isDraining()) {178 int done = pendingSessions.decrementAndGet();179 if (done <= 0) {180 LOG.info("Firing node drain complete message");181 bus.fire(new NodeDrainComplete(this.getId()));182 }183 }184 }));185 new JMXHelper().register(this);186 }187 public static Builder builder(188 Tracer tracer,189 EventBus bus,190 URI uri,191 URI gridUri,192 Secret registrationSecret) {193 return new Builder(tracer, bus, uri, gridUri, registrationSecret);194 }195 @Override196 public boolean isReady() {197 return bus.isReady();198 }199 @VisibleForTesting200 @ManagedAttribute(name = "CurrentSessions")201 public int getCurrentSessionCount() {202 // It seems wildly unlikely we'll overflow an int203 return Math.toIntExact(currentSessions.size());204 }205 @ManagedAttribute(name = "MaxSessions")206 public int getMaxSessionCount() {207 return maxSessionCount;208 }209 @ManagedAttribute(name = "Status")210 public Availability getAvailability() {211 return isDraining() ? DRAINING : UP;212 }213 @ManagedAttribute(name = "TotalSlots")214 public int getTotalSlots() {215 return factories.size();216 }217 @ManagedAttribute(name = "UsedSlots")218 public long getUsedSlots() {219 return factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();220 }221 @ManagedAttribute(name = "Load")222 public float getLoad() {223 long inUse = factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();224 return inUse / (float) maxSessionCount * 100f;225 }226 @ManagedAttribute(name = "RemoteNodeUri")227 public URI getExternalUri() {228 return this.getUri();229 }230 @ManagedAttribute(name = "GridUri")231 public URI getGridUri() {232 return this.gridUri;233 }234 @ManagedAttribute(name = "NodeId")235 public String getNodeId() {236 return getId().toString();237 }238 @Override239 public boolean isSupporting(Capabilities capabilities) {240 return factories.parallelStream().anyMatch(factory -> factory.test(capabilities));241 }242 @Override243 public Either<WebDriverException, CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {244 Require.nonNull("Session request", sessionRequest);245 try (Span span = tracer.getCurrentContext().createSpan("node.new_session")) {246 Map<String, EventAttributeValue> attributeMap = new HashMap<>();247 attributeMap248 .put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName()));249 attributeMap.put("session.request.capabilities",250 EventAttribute.setValue(sessionRequest.getDesiredCapabilities().toString()));251 attributeMap.put("session.request.downstreamdialect",252 EventAttribute.setValue(sessionRequest.getDownstreamDialects().toString()));253 int currentSessionCount = getCurrentSessionCount();254 span.setAttribute("current.session.count", currentSessionCount);255 attributeMap.put("current.session.count", EventAttribute.setValue(currentSessionCount));256 if (getCurrentSessionCount() >= maxSessionCount) {257 span.setAttribute("error", true);258 span.setStatus(Status.RESOURCE_EXHAUSTED);259 attributeMap.put("max.session.count", EventAttribute.setValue(maxSessionCount));260 span.addEvent("Max session count reached", attributeMap);261 return Either.left(new RetrySessionRequestException("Max session count reached."));262 }263 if (isDraining()) {264 span.setStatus(Status.UNAVAILABLE.withDescription("The node is draining. Cannot accept new sessions."));265 return Either.left(266 new RetrySessionRequestException("The node is draining. Cannot accept new sessions."));267 }268 // Identify possible slots to use as quickly as possible to enable concurrent session starting269 SessionSlot slotToUse = null;270 synchronized (factories) {271 for (SessionSlot factory : factories) {272 if (!factory.isAvailable() || !factory.test(sessionRequest.getDesiredCapabilities())) {273 continue;274 }275 factory.reserve();276 slotToUse = factory;277 break;278 }279 }280 if (slotToUse == null) {281 span.setAttribute("error", true);282 span.setStatus(Status.NOT_FOUND);283 span.addEvent("No slot matched the requested capabilities. ", attributeMap);284 return Either.left(285 new RetrySessionRequestException("No slot matched the requested capabilities."));286 }287 Either<WebDriverException, ActiveSession> possibleSession = slotToUse.apply(sessionRequest);288 if (possibleSession.isRight()) {289 ActiveSession session = possibleSession.right();290 currentSessions.put(session.getId(), slotToUse);291 SessionId sessionId = session.getId();292 Capabilities caps = session.getCapabilities();293 SESSION_ID.accept(span, sessionId);294 CAPABILITIES.accept(span, caps);295 String downstream = session.getDownstreamDialect().toString();296 String upstream = session.getUpstreamDialect().toString();297 String sessionUri = session.getUri().toString();298 span.setAttribute(AttributeKey.DOWNSTREAM_DIALECT.getKey(), downstream);299 span.setAttribute(AttributeKey.UPSTREAM_DIALECT.getKey(), upstream);300 span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri);301 // The session we return has to look like it came from the node, since we might be dealing302 // with a webdriver implementation that only accepts connections from localhost303 Session externalSession = createExternalSession(304 session,305 externalUri,306 slotToUse.isSupportingCdp() || caps.getCapability("se:cdp") != null);307 return Either.right(new CreateSessionResponse(308 externalSession,309 getEncoder(session.getDownstreamDialect()).apply(externalSession)));310 } else {311 slotToUse.release();312 span.setAttribute("error", true);313 span.addEvent("Unable to create session with the driver", attributeMap);314 return Either.left(possibleSession.left());315 }316 }317 }318 @Override319 public boolean isSessionOwner(SessionId id) {320 Require.nonNull("Session ID", id);321 return currentSessions.getIfPresent(id) != null;322 }323 @Override324 public Session getSession(SessionId id) throws NoSuchSessionException {325 Require.nonNull("Session ID", id);326 SessionSlot slot = currentSessions.getIfPresent(id);327 if (slot == null) {328 throw new NoSuchSessionException("Cannot find session with id: " + id);329 }330 return createExternalSession(slot.getSession(), externalUri, slot.isSupportingCdp());331 }332 @Override333 public TemporaryFilesystem getTemporaryFilesystem(SessionId id) throws IOException {334 try {335 return tempFileSystems.get(id, () -> TemporaryFilesystem.getTmpFsBasedOn(336 TemporaryFilesystem.getDefaultTmpFS().createTempDir("session", id.toString())));337 } catch (ExecutionException e) {338 throw new IOException(e);339 }340 }341 @Override342 public HttpResponse executeWebDriverCommand(HttpRequest req) {343 // True enough to be good enough344 SessionId id = getSessionId(req.getUri()).map(SessionId::new)345 .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req));346 SessionSlot slot = currentSessions.getIfPresent(id);347 if (slot == null) {348 throw new NoSuchSessionException("Cannot find session with id: " + id);349 }350 HttpResponse toReturn = slot.execute(req);351 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {352 stop(id);353 }354 return toReturn;355 }356 @Override357 public HttpResponse uploadFile(HttpRequest req, SessionId id) {358 Map<String, Object> incoming = JSON.toType(string(req), Json.MAP_TYPE);359 File tempDir;360 try {361 TemporaryFilesystem tempfs = getTemporaryFilesystem(id);362 tempDir = tempfs.createTempDir("upload", "file");363 Zip.unzip((String) incoming.get("file"), tempDir);364 } catch (IOException e) {365 throw new UncheckedIOException(e);366 }367 // Select the first file368 File[] allFiles = tempDir.listFiles();369 if (allFiles == null) {370 throw new WebDriverException(371 String.format("Cannot access temporary directory for uploaded files %s", tempDir));372 }373 if (allFiles.length != 1) {374 throw new WebDriverException(375 String.format("Expected there to be only 1 file. There were: %s", allFiles.length));376 }377 ImmutableMap<String, Object> result = ImmutableMap.of(378 "value", allFiles[0].getAbsolutePath());379 return new HttpResponse().setContent(asJson(result));380 }381 @Override382 public void stop(SessionId id) throws NoSuchSessionException {383 Require.nonNull("Session ID", id);384 SessionSlot slot = currentSessions.getIfPresent(id);385 if (slot == null) {386 throw new NoSuchSessionException("Cannot find session with id: " + id);387 }388 killSession(slot);389 tempFileSystems.invalidate(id);390 }391 private Session createExternalSession(ActiveSession other, URI externalUri, boolean isSupportingCdp) {392 Capabilities toUse = ImmutableCapabilities.copyOf(other.getCapabilities());393 // Rewrite the se:options if necessary to send the cdp url back394 if (isSupportingCdp) {395 String cdpPath = String.format("/session/%s/se/cdp", other.getId());396 toUse = new PersistentCapabilities(toUse).setCapability("se:cdp", rewrite(cdpPath));397 }398 return new Session(other.getId(), externalUri, other.getStereotype(), toUse, Instant.now());399 }400 private URI rewrite(String path) {401 try {402 return new URI(403 "ws",404 gridUri.getUserInfo(),405 gridUri.getHost(),406 gridUri.getPort(),407 path,408 null,409 null);410 } catch (URISyntaxException e) {411 throw new RuntimeException(e);412 }413 }414 private void killSession(SessionSlot slot) {415 currentSessions.invalidate(slot.getSession().getId());416 // Attempt to stop the session417 if (!slot.isAvailable()) {418 slot.stop();419 }420 }421 @Override422 public NodeStatus getStatus() {423 Set<Slot> slots = factories.stream()424 .map(slot -> {425 Instant lastStarted = Instant.EPOCH;426 Optional<Session> session = Optional.empty();427 if (!slot.isAvailable()) {428 ActiveSession activeSession = slot.getSession();429 if (activeSession != null) {430 lastStarted = activeSession.getStartTime();431 session = Optional.of(432 new Session(433 activeSession.getId(),434 activeSession.getUri(),435 slot.getStereotype(),436 activeSession.getCapabilities(),437 activeSession.getStartTime()));438 }439 }440 return new Slot(441 new SlotId(getId(), slot.getId()),442 slot.getStereotype(),443 lastStarted,444 session);445 })446 .collect(toImmutableSet());447 return new NodeStatus(448 getId(),449 externalUri,450 maxSessionCount,451 slots,452 isDraining() ? DRAINING : UP,453 heartbeatPeriod,454 getNodeVersion(),455 getOsInfo());456 }457 @Override458 public HealthCheck getHealthCheck() {459 return healthCheck;460 }461 @Override462 public void drain() {463 bus.fire(new NodeDrainStarted(getId()));464 draining = true;465 int currentSessionCount = getCurrentSessionCount();466 if (currentSessionCount == 0) {467 LOG.info("Firing node drain complete message");468 bus.fire(new NodeDrainComplete(getId()));469 } else {470 pendingSessions.set(currentSessionCount);471 }472 }473 private Map<String, Object> toJson() {474 return ImmutableMap.of(475 "id", getId(),476 "uri", externalUri,477 "maxSessions", maxSessionCount,478 "draining", isDraining(),479 "capabilities", factories.stream()480 .map(SessionSlot::getStereotype)481 .collect(Collectors.toSet()));482 }483 public static class Builder {484 private final Tracer tracer;485 private final EventBus bus;486 private final URI uri;487 private final URI gridUri;488 private final Secret registrationSecret;489 private final ImmutableList.Builder<SessionSlot> factories;490 private int maxCount = NodeOptions.DEFAULT_MAX_SESSIONS;491 private Ticker ticker = Ticker.systemTicker();492 private Duration sessionTimeout = Duration.ofSeconds(NodeOptions.DEFAULT_SESSION_TIMEOUT);...

Full Screen

Full Screen

Source:KubernetesNode.java Github

copy

Full Screen

...92 sessionCleanup.submit(currentSessions::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));93 var regularHeartBeat = new Regularly("Heartbeat Node: " + uri);94 regularHeartBeat.submit(() -> bus.fire(new NodeHeartBeatEvent(getStatus())), heartbeatPeriod, heartbeatPeriod);95 bus.addListener(SessionClosedEvent.listener(id -> {96 if (this.isDraining() && pendingSessions.decrementAndGet() <= 0) {97 LOG.info("Firing node drain complete message");98 bus.fire(new NodeDrainComplete(this.getId()));99 }100 }));101 additionalRoutes = combine(102 get("/downloads/{sessionId}/{fileName}")103 .to(params -> new GetFile(getActiveSession(sessionIdFrom(params)), fileNameFrom(params))),104 delete("/downloads/{sessionId}/{fileName}")105 .to(params -> new DeleteFile(getActiveSession(sessionIdFrom(params)), fileNameFrom(params))),106 get("/downloads/{sessionId}")107 .to(params -> new ListFiles(getActiveSession(sessionIdFrom(params)))),108 delete("/downloads/{sessionId}")109 .to(params -> new DeleteFiles(getActiveSession(sessionIdFrom(params)))));110 Runtime.getRuntime().addShutdownHook(new Thread(this::stopAllSessions));111 new JMXHelper().register(this);112 }113 public static Node create(Config config) {114 var loggingOptions = new LoggingOptions(config);115 var eventOptions = new EventBusOptions(config);116 var serverOptions = new BaseServerOptions(config);117 var secretOptions = new SecretOptions(config);118 var networkOptions = new NetworkOptions(config);119 var k8sOptions = new KubernetesOptions(config);120 var tracer = loggingOptions.getTracer();121 var bus = eventOptions.getEventBus();122 var clientFactory = networkOptions.getHttpClientFactory(tracer);123 var k8s = new KubernetesDriver(new DefaultKubernetesClient());124 var factories = createFactories(k8sOptions, tracer, clientFactory, bus, k8s);125 LOG.info("Creating kubernetes node");126 return new KubernetesNode(tracer, bus, secretOptions.getRegistrationSecret(), new NodeId(UUID.randomUUID()),127 serverOptions.getExternalUri(), factories, k8sOptions.getMaxSessions(), k8sOptions.getSessionTimeout(),128 k8sOptions.getHeartbeatPeriod()129 );130 }131 static List<SessionSlot> createFactories(KubernetesOptions k8sOptions, Tracer tracer,132 HttpClient.Factory clientFactory, EventBus eventBus,133 KubernetesDriver driver) {134 var configs = k8sOptions.getConfigs();135 if (configs.isEmpty()) {136 throw new ConfigException("Unable to find kubernetes configs");137 }138 return configs.stream().flatMap(c -> Collections.nCopies(k8sOptions.getMaxSessions(), c).stream())139 .map(config -> {140 var image = config.getImage();141 var stereoType = config.getStereoType();142 var factory = new KubernetesSessionFactory(tracer, clientFactory, driver,143 k8sOptions.getWorkerResourceRequests(), image, stereoType, k8sOptions.getVideoImage(),144 k8sOptions.getVideosPath());145 return new SessionSlot(eventBus, stereoType, factory);146 }).collect(Collectors.toList());147 }148 @Override149 public Either<WebDriverException, CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {150 try (var span = tracer.getCurrentContext().createSpan("kubernetes_node.new_session")) {151 Map<String, EventAttributeValue> attributeMap = new HashMap<>();152 attributeMap.put(LOGGER_CLASS.getKey(), setValue(getClass().getName()));153 attributeMap.put("session.request.capabilities", setValue(sessionRequest.getDesiredCapabilities().toString()));154 attributeMap.put("session.request.downstreamdialect", setValue(sessionRequest.getDownstreamDialects()155 .toString()));156 var currentSessionCount = getCurrentSessionCount();157 span.setAttribute("current.session.count", currentSessionCount);158 attributeMap.put("current.session.count", setValue(currentSessionCount));159 if (getCurrentSessionCount() >= maxSessionCount) {160 span.setAttribute("error", true);161 span.setStatus(Status.RESOURCE_EXHAUSTED);162 attributeMap.put("max.session.count", setValue(maxSessionCount));163 span.addEvent("Max session count reached", attributeMap);164 return Either.left(new RetrySessionRequestException("Max session count reached."));165 }166 SessionSlot slotToUse = null;167 synchronized (factories) {168 for (var factory : factories) {169 if (!factory.isAvailable() || !factory.test(sessionRequest.getDesiredCapabilities())) {170 continue;171 }172 factory.reserve();173 slotToUse = factory;174 break;175 }176 }177 if (slotToUse == null) {178 span.setAttribute("error", true);179 span.setStatus(Status.NOT_FOUND);180 span.addEvent("No slot matched the requested capabilities. ", attributeMap);181 return Either.left(new RetrySessionRequestException("No slot matched the requested capabilities."));182 }183 Either<WebDriverException, ActiveSession> possibleSession = slotToUse.apply(sessionRequest);184 if (possibleSession.isRight()) {185 var session = possibleSession.right();186 currentSessions.put(session.getId(), slotToUse);187 SESSION_ID.accept(span, session.getId());188 var caps = session.getCapabilities();189 CAPABILITIES.accept(span, caps);190 span.setAttribute(DOWNSTREAM_DIALECT.getKey(), session.getDownstreamDialect().toString());191 span.setAttribute(UPSTREAM_DIALECT.getKey(), session.getUpstreamDialect().toString());192 span.setAttribute(SESSION_URI.getKey(), session.getUri().toString());193 var isSupportingCdp = slotToUse.isSupportingCdp() || caps.getCapability("se:cdp") != null;194 var externalSession = createExternalSession(session, uri, isSupportingCdp);195 return Either.right(new CreateSessionResponse(externalSession,196 getEncoder(session.getDownstreamDialect()).apply(externalSession)));197 } else {198 slotToUse.release();199 span.setAttribute("error", true);200 span.addEvent("Unable to create session with the driver", attributeMap);201 return Either.left(possibleSession.left());202 }203 }204 }205 @Override206 public HttpResponse executeWebDriverCommand(HttpRequest req) {207 var id = getSessionId(req.getUri()).map(SessionId::new)208 .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req));209 var slot = getSessionSlot(id);210 var toReturn = slot.execute(req);211 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {212 stop(id);213 }214 return toReturn;215 }216 @Override217 public Session getSession(SessionId id) throws NoSuchSessionException {218 var slot = getSessionSlot(id);219 return createExternalSession(slot.getSession(), uri, slot.isSupportingCdp());220 }221 @Override222 public HttpResponse uploadFile(HttpRequest req, SessionId id) {223 return executeWebDriverCommand(req);224 }225 @Override226 public void stop(SessionId id) throws NoSuchSessionException {227 getSessionSlot(id);228 currentSessions.invalidate(id);229 }230 @Override231 public boolean isSessionOwner(SessionId id) {232 return currentSessions.getIfPresent(id) != null;233 }234 @Override235 public boolean isSupporting(Capabilities capabilities) {236 return factories.parallelStream().anyMatch(factory -> factory.test(capabilities));237 }238 @Override239 public NodeStatus getStatus() {240 return new NodeStatus(getId(), uri, maxSessionCount, getSlots(), isDraining() ? DRAINING : UP, heartbeatPeriod,241 getNodeVersion(), getOsInfo());242 }243 @Override244 public HealthCheck getHealthCheck() {245 var availability = isDraining() ? DRAINING : UP;246 return () -> new HealthCheck.Result(availability, String.format("%s is %s", uri,247 availability.name().toLowerCase()));248 }249 @Override250 public void drain() {251 bus.fire(new NodeDrainStarted(getId()));252 draining = true;253 var currentSessionCount = getCurrentSessionCount();254 if (currentSessionCount == 0) {255 LOG.info("Firing node drain complete message");256 bus.fire(new NodeDrainComplete(getId()));257 } else {258 pendingSessions.set(currentSessionCount);259 }...

Full Screen

Full Screen

Source:OneShotNode.java Github

copy

Full Screen

...293 Instant.EPOCH,294 driver == null ?295 Optional.empty() :296 Optional.of(new Session(sessionId, getUri(), stereotype, capabilities, Instant.now())))),297 isDraining() ? DRAINING : UP);298 }299 @Override300 public void drain() {301 events.fire(new NodeDrainStarted(getId()));302 draining = true;303 }304 @Override305 public HealthCheck getHealthCheck() {306 return () -> new HealthCheck.Result(isDraining() ? DRAINING : UP, "Everything is fine");307 }308 @Override309 public boolean isReady() {310 return events.isReady();311 }312}...

Full Screen

Full Screen

Source:LocalDistributorTest.java Github

copy

Full Screen

...169 clientFactory,170 new LocalSessionMap(tracer, bus),171 null);172 distributor.add(localNode);173 assertThat(localNode.isDraining()).isFalse();174 //Check the size - there should be one node175 DistributorStatus statusBefore = distributor.getStatus();176 Set<NodeStatus> nodesBefore = statusBefore.getNodes();177 assertThat(nodesBefore.size()).isEqualTo(1);178 NodeStatus nodeBefore = nodesBefore.iterator().next();179 assertThat(nodeBefore.getAvailability()).isNotEqualTo(DRAINING);180 distributor.drain(localNode.getId());181 assertThat(localNode.isDraining()).isTrue();182 //Recheck the status - there should still be no node, it is removed183 DistributorStatus statusAfter = distributor.getStatus();184 Set<NodeStatus> nodesAfter = statusAfter.getNodes();185 assertThat(nodesAfter.size()).isEqualTo(0);186 }187 @Test188 public void testDrainNodeFromNode() {189 assertThat(localNode.isDraining()).isFalse();190 Distributor191 distributor =192 new LocalDistributor(tracer, bus, clientFactory, new LocalSessionMap(tracer, bus), null);193 distributor.add(localNode);194 localNode.drain();195 assertThat(localNode.isDraining()).isTrue();196 }197 private class Handler extends Session implements HttpHandler {198 private Handler(Capabilities capabilities) {199 super(new SessionId(UUID.randomUUID()), uri, new ImmutableCapabilities(), capabilities, Instant.now());200 }201 @Override202 public HttpResponse execute(HttpRequest req) throws UncheckedIOException {203 return new HttpResponse();204 }205 }206}...

Full Screen

Full Screen

Source:Host.java Github

copy

Full Screen

...70 HealthCheck.Result result = healthCheck.check();71 Availability current = result.isAlive() ? UP : DOWN;72 Availability previous = setHostStatus(current);73 //If the node has been set to maintenance mode, set the status here as draining74 if (node.isDraining() || previous == DRAINING) {75 // We want to continue to allow the node to drain.76 setHostStatus(DRAINING);77 return;78 }79 if (current != previous) {80 LOG.info(String.format(81 "Changing status of node %s from %s to %s. Reason: %s",82 node.getId(),83 previous,84 current,85 result.getMessage()));86 }87 };88 bus.addListener(SESSION_CLOSED, event -> {89 SessionId id = event.getData(SessionId.class);90 this.slots.forEach(slot -> slot.onEnd(id));91 });92 update(node.getStatus());93 }94 public void update(NodeStatus status) {95 Require.nonNull("Node status", status);96 Lock writeLock = lock.writeLock();97 writeLock.lock();98 try {99 this.slots = status.getSlots().stream()100 .map(slot -> new Slot(node, slot.getStereotype(), slot.getSession().isPresent() ? ACTIVE : AVAILABLE))101 .collect(toImmutableSet());102 // By definition, we can never have more sessions than we have slots available103 this.maxSessionCount = Math.min(this.slots.size(), status.getMaxSessionCount());104 } finally {105 writeLock.unlock();106 }107 }108 public NodeId getId() {109 return nodeId;110 }111 public DistributorStatus.NodeSummary asSummary() {112 Map<Capabilities, Integer> stereotypes = new HashMap<>();113 Map<Capabilities, Integer> used = new HashMap<>();114 Set<Session> activeSessions = new HashSet<>();115 slots.forEach(slot -> {116 stereotypes.compute(slot.getStereotype(), (key, curr) -> curr == null ? 1 : curr + 1);117 if (slot.getStatus() != AVAILABLE) {118 used.compute(slot.getStereotype(), (key, curr) -> curr == null ? 1 : curr + 1);119 activeSessions.add(slot.getCurrentSession());120 }121 });122 return new DistributorStatus.NodeSummary(123 nodeId,124 uri,125 getHostStatus(),126 maxSessionCount,127 stereotypes,128 used,129 activeSessions);130 }131 public Availability getHostStatus() {132 return status;133 }134 /**135 * @return The previous status of the node.136 */137 private Availability setHostStatus(Availability status) {138 Availability toReturn = this.status;139 this.status = Require.nonNull("Status", status);140 return toReturn;141 }142 /**143 * @return The previous status of the node if it not able to drain else returning draining status.144 */145 public Availability drainHost() {146 Availability prev = this.status;147 // Drain the node148 if (!node.isDraining()) {149 node.drain();150 }151 // For some reason, it is still not draining then do not update the host status152 if (!node.isDraining()) {153 return prev;154 } else {155 this.status = DRAINING;156 return DRAINING;157 }158 }159 /**160 * @return Whether or not the host has slots available for the requested capabilities.161 */162 public boolean hasCapacity(Capabilities caps) {163 Lock read = lock.readLock();164 read.lock();165 try {166 long count = slots.stream()...

Full Screen

Full Screen

Source:Drain.java Github

copy

Full Screen

...33 @Override34 public HttpResponse execute(HttpRequest req) throws UncheckedIOException {35 this.node.drain();36 HttpResponse response = new HttpResponse();37 if (this.node.isDraining()) {38 response.setStatus(HTTP_OK);39 } else {40 response.setStatus(HTTP_INTERNAL_ERROR);41 }42 return response;43 }44}...

Full Screen

Full Screen

isDraining

Using AI Code Generation

copy

Full Screen

1import org.openqa.selenium.grid.config.Config;2import org.openqa.selenium.grid.config.MapConfig;3import org.openqa.selenium.grid.node.LocalNode;4import org.openqa.selenium.grid.node.Node;5import org.openqa.selenium.grid.node.NodeOptions;6import org.openqa.selenium.grid.node.remote.RemoteNode;7import org.openqa.selenium.grid.server.BaseServerOptions;8import org.openqa.selenium.grid.server.Server;9import org.openqa.selenium.grid.server.ServerOptions;10import org.openqa.selenium.grid.web.Routable;11import org.openqa.selenium.internal.Require;12import org.openqa.selenium.json.Json;13import org.openqa.selenium.remote.http.HttpHandler;14import org.openqa.selenium.remote.http.HttpResponse;15import org.openqa.selenium.remote.http.Route;16import org.openqa.selenium.remote.tracing.Tracer;17import java.net.URI;18import java.util.Map;19import java.util.Objects;20import java.util.concurrent.CompletableFuture;21import java.util.concurrent.ConcurrentHashMap;22import java.util.concurrent.atomic.AtomicBoolean;23import java.util.logging.Logger;24public class CustomNode implements Node {25 private static final Logger LOG = Logger.getLogger(CustomNode.class.getName());26 private final URI externalUri;27 private final AtomicBoolean isDraining = new AtomicBoolean(false);28 private final AtomicBoolean isDown = new AtomicBoolean(false);29 private final Map<String, Object> status = new ConcurrentHashMap<>();30 private final Server<?> server;31 private final Node delegate;32 public CustomNode(Tracer tracer, URI externalUri, Node delegate) {33 this.externalUri = Require.nonNull("External URI", externalUri);34 this.delegate = Require.nonNull("Delegate", delegate);35 this.server = new Server<>(new ServerOptions(new MapConfig(ImmutableMap.of(36 "server.address", externalUri.getHost(),37 "server.port", externalUri.getPort(),38 "server.servlets", "custom.node.status"))), tracer, new CustomStatus(this, tracer));39 }40 public URI getUri() {41 return externalUri;42 }43 public boolean isDraining() {44 return isDraining.get();45 }46 public boolean isDown() {47 return isDown.get();48 }49 public Map<String, Object> getStatus() {50 return status;51 }52 public void start() {53 server.start();54 delegate.start();55 }56 public void stop() {57 server.stop();58 delegate.stop();

Full Screen

Full Screen

isDraining

Using AI Code Generation

copy

Full Screen

1public void isDraining() throws IOException, InterruptedException {2 WebDriver driver = new ChromeDriver();3 Node node = new Node();4 boolean isDraining = node.isDraining();5 driver.quit();6}7public void isDraining() throws IOException, InterruptedException {8 WebDriver driver = new ChromeDriver();9 Node node = new Node();10 boolean isDraining = node.isDraining();11 driver.quit();12}13public void isDraining() throws IOException, InterruptedException {14 WebDriver driver = new ChromeDriver();15 Node node = new Node();16 boolean isDraining = node.isDraining();17 driver.quit();18}19public void isDraining() throws IOException, InterruptedException {20 WebDriver driver = new ChromeDriver();21 Node node = new Node();22 boolean isDraining = node.isDraining();23 driver.quit();24}25public void isDraining() throws IOException, InterruptedException {26 WebDriver driver = new ChromeDriver();27 Node node = new Node();28 boolean isDraining = node.isDraining();29 driver.quit();30}31public void isDraining() throws IOException, InterruptedException {32 WebDriver driver = new ChromeDriver();33 Node node = new Node();34 boolean isDraining = node.isDraining();

Full Screen

Full Screen

isDraining

Using AI Code Generation

copy

Full Screen

1import org.openqa.selenium.grid.node.Node;2public class NodeDraining {3 public static void main(String[] args) throws Exception {4 boolean isNodeDraining = node.isDraining();5 if (isNodeDraining) {6 System.out.println("Node is draining");7 Thread.sleep(10000);8 isNodeDraining = node.isDraining();9 }10 System.out.println("Node is not draining");11 }12}

Full Screen

Full Screen

Selenium 4 Tutorial:

LambdaTest’s Selenium 4 tutorial is covering every aspects of Selenium 4 testing with examples and best practices. Here you will learn basics, such as how to upgrade from Selenium 3 to Selenium 4, to some advanced concepts, such as Relative locators and Selenium Grid 4 for Distributed testing. Also will learn new features of Selenium 4, such as capturing screenshots of specific elements, opening a new tab or window on the browser, and new protocol adoptions.

Chapters:

  1. Upgrading From Selenium 3 To Selenium 4?: In this chapter, learn in detail how to update Selenium 3 to Selenium 4 for Java binding. Also, learn how to upgrade while using different build tools such as Maven or Gradle and get comprehensive guidance for upgrading Selenium.

  2. What’s New In Selenium 4 & What’s Being Deprecated? : Get all information about new implementations in Selenium 4, such as W3S protocol adaption, Optimized Selenium Grid, and Enhanced Selenium IDE. Also, learn what is deprecated for Selenium 4, such as DesiredCapabilites and FindsBy methods, etc.

  3. Selenium 4 With Python: Selenium supports all major languages, such as Python, C#, Ruby, and JavaScript. In this chapter, learn how to install Selenium 4 for Python and the features of Python in Selenium 4, such as Relative locators, Browser manipulation, and Chrom DevTool protocol.

  4. Selenium 4 Is Now W3C Compliant: JSON Wireframe protocol is retiring from Selenium 4, and they are adopting W3C protocol to learn in detail about the advantages and impact of these changes.

  5. How To Use Selenium 4 Relative Locator? : Selenium 4 came with new features such as Relative Locators that allow constructing locators with reference and easily located constructors nearby. Get to know its different use cases with examples.

  6. Selenium Grid 4 Tutorial For Distributed Testing: Selenium Grid 4 allows you to perform tests over different browsers, OS, and device combinations. It also enables parallel execution browser testing, reads up on various features of Selenium Grid 4 and how to download it, and runs a test on Selenium Grid 4 with best practices.

  7. Selenium Video Tutorials: Binge on video tutorials on Selenium by industry experts to get step-by-step direction from automating basic to complex test scenarios with Selenium.

Selenium 101 certifications:

LambdaTest also provides certification for Selenium testing to accelerate your career in Selenium automation testing.

Run Selenium automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful