How to use String method of tdhttp_test Package

Best Go-testdeep code snippet using tdhttp_test.String

example_test.go

Source:example_test.go Github

copy

Full Screen

1// Copyright (c) 2020, Maxime Soulé2// All rights reserved.3//4// This source code is licensed under the BSD-style license found in the5// LICENSE file in the root directory of this source tree.6package tdhttp_test7import (8 "encoding/json"9 "encoding/xml"10 "fmt"11 "io"12 "net/http"13 "net/url"14 "strconv"15 "strings"16 "sync"17 "testing"18 "time"19 "github.com/maxatome/go-testdeep/helpers/tdhttp"20 "github.com/maxatome/go-testdeep/td"21)22func Example() {23 t := &testing.T{}24 // Our API handle Persons with 3 routes:25 // - POST /person26 // - GET /person/{personID}27 // - DELETE /person/{personID}28 // Person describes a person.29 type Person struct {30 ID int64 `json:"id,omitempty" xml:"ID,omitempty"`31 Name string `json:"name" xml:"Name"`32 Age int `json:"age" xml:"Age"`33 CreatedAt *time.Time `json:"created_at,omitempty" xml:"CreatedAt,omitempty"`34 }35 // Error is returned to the client in case of error.36 type Error struct {37 Mesg string `json:"message" xml:"Message"`38 Code int `json:"code" xml:"Code"`39 }40 // Our µDB :)41 var mu sync.Mutex42 personByID := map[int64]*Person{}43 personByName := map[string]*Person{}44 var lastID int6445 // reply is a helper to send responses.46 reply := func(w http.ResponseWriter, status int, contentType string, body any) {47 if body == nil {48 w.WriteHeader(status)49 return50 }51 w.Header().Set("Content-Type", contentType)52 w.WriteHeader(status)53 switch contentType {54 case "application/json":55 json.NewEncoder(w).Encode(body) //nolint: errcheck56 case "application/xml":57 xml.NewEncoder(w).Encode(body) //nolint: errcheck58 default: // text/plain59 fmt.Fprintf(w, "%+v", body)60 }61 }62 // Our API63 mux := http.NewServeMux()64 // POST /person65 mux.HandleFunc("/person", func(w http.ResponseWriter, req *http.Request) {66 if req.Method != http.MethodPost {67 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)68 return69 }70 if req.Body == nil {71 http.Error(w, "Bad request", http.StatusBadRequest)72 return73 }74 defer req.Body.Close()75 var in Person76 var contentType string77 switch req.Header.Get("Content-Type") {78 case "application/json":79 err := json.NewDecoder(req.Body).Decode(&in)80 if err != nil {81 http.Error(w, "Bad request", http.StatusBadRequest)82 return83 }84 case "application/xml":85 err := xml.NewDecoder(req.Body).Decode(&in)86 if err != nil {87 http.Error(w, "Bad request", http.StatusBadRequest)88 return89 }90 case "application/x-www-form-urlencoded":91 b, err := io.ReadAll(req.Body)92 if err != nil {93 http.Error(w, "Bad request", http.StatusBadRequest)94 return95 }96 v, err := url.ParseQuery(string(b))97 if err != nil {98 http.Error(w, "Bad request", http.StatusBadRequest)99 return100 }101 in.Name = v.Get("name")102 in.Age, err = strconv.Atoi(v.Get("age"))103 if err != nil {104 http.Error(w, "Bad request", http.StatusBadRequest)105 return106 }107 default:108 http.Error(w, "Unsupported media type", http.StatusUnsupportedMediaType)109 return110 }111 contentType = req.Header.Get("Accept")112 if in.Name == "" || in.Age <= 0 {113 reply(w, http.StatusBadRequest, contentType, Error{114 Mesg: "Empty name or bad age",115 Code: http.StatusBadRequest,116 })117 return118 }119 mu.Lock()120 defer mu.Unlock()121 if personByName[in.Name] != nil {122 reply(w, http.StatusConflict, contentType, Error{123 Mesg: "Person already exists",124 Code: http.StatusConflict,125 })126 return127 }128 lastID++129 in.ID = lastID130 now := time.Now()131 in.CreatedAt = &now132 personByID[in.ID] = &in133 personByName[in.Name] = &in134 reply(w, http.StatusCreated, contentType, in)135 })136 // GET /person/{id}137 // DELETE /person/{id}138 mux.HandleFunc("/person/", func(w http.ResponseWriter, req *http.Request) {139 id, err := strconv.ParseInt(strings.TrimPrefix(req.URL.Path, "/person/"), 10, 64)140 if err != nil {141 http.Error(w, "Bad request", http.StatusBadRequest)142 return143 }144 accept := req.Header.Get("Accept")145 mu.Lock()146 defer mu.Unlock()147 if personByID[id] == nil {148 reply(w, http.StatusNotFound, accept, Error{149 Mesg: "Person does not exist",150 Code: http.StatusNotFound,151 })152 return153 }154 switch req.Method {155 case http.MethodGet:156 reply(w, http.StatusOK, accept, personByID[id])157 case http.MethodDelete:158 delete(personByID, id)159 reply(w, http.StatusNoContent, "", nil)160 default:161 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)162 }163 })164 //165 // Let's test our API166 //167 ta := tdhttp.NewTestAPI(t, mux)168 // Re-usable custom operator to check Content-Type header169 contentTypeIs := func(ct string) td.TestDeep {170 return td.SuperMapOf(http.Header{"Content-Type": []string{ct}}, nil)171 }172 //173 // Person not found174 //175 ta.Get("/person/42", "Accept", "application/json").176 Name("GET /person/42 - JSON").177 CmpStatus(404).178 CmpHeader(contentTypeIs("application/json")).179 CmpJSONBody(Error{180 Mesg: "Person does not exist",181 Code: 404,182 })183 fmt.Println("GET /person/42 - JSON:", !ta.Failed())184 ta.Get("/person/42", "Accept", "application/xml").185 Name("GET /person/42 - XML").186 CmpStatus(404).187 CmpHeader(contentTypeIs("application/xml")).188 CmpXMLBody(Error{189 Mesg: "Person does not exist",190 Code: 404,191 })192 fmt.Println("GET /person/42 - XML:", !ta.Failed())193 ta.Get("/person/42", "Accept", "text/plain").194 Name("GET /person/42 - raw").195 CmpStatus(404).196 CmpHeader(contentTypeIs("text/plain")).197 CmpBody("{Mesg:Person does not exist Code:404}")198 fmt.Println("GET /person/42 - raw:", !ta.Failed())199 //200 // Create a Person201 //202 var bobID int64203 ta.PostXML("/person", Person{Name: "Bob", Age: 32},204 "Accept", "application/xml").205 Name("POST /person - XML").206 CmpStatus(201).207 CmpHeader(contentTypeIs("application/xml")).208 CmpXMLBody(Person{ // using operator anchoring directly in literal209 ID: ta.A(td.Catch(&bobID, td.NotZero()), int64(0)).(int64),210 Name: "Bob",211 Age: 32,212 CreatedAt: ta.A(td.Ptr(td.Between(ta.SentAt(), time.Now()))).(*time.Time),213 })214 fmt.Printf("POST /person - XML: %t → Bob ID=%d\n", !ta.Failed(), bobID)215 var aliceID int64216 ta.PostJSON("/person", Person{Name: "Alice", Age: 35},217 "Accept", "application/json").218 Name("POST /person - JSON").219 CmpStatus(201).220 CmpHeader(contentTypeIs("application/json")).221 CmpJSONBody(td.JSON(` // using JSON operator (yes comment allowed in JSON!)222{223 "id": $1,224 "name": "Alice",225 "age": 35,226 "created_at": $2227}`,228 td.Catch(&aliceID, td.NotZero()),229 td.Smuggle(func(date string) (time.Time, error) {230 return time.Parse(time.RFC3339Nano, date)231 }, td.Between(ta.SentAt(), time.Now()))))232 fmt.Printf("POST /person - JSON: %t → Alice ID=%d\n", !ta.Failed(), aliceID)233 var brittID int64234 ta.PostForm("/person",235 url.Values{236 "name": []string{"Britt"},237 "age": []string{"29"},238 },239 "Accept", "text/plain").240 Name("POST /person - raw").241 CmpStatus(201).242 CmpHeader(contentTypeIs("text/plain")).243 // using Re (= Regexp) operator244 CmpBody(td.Re(`\{ID:(\d+) Name:Britt Age:29 CreatedAt:.*\}\z`,245 td.Smuggle(func(groups []string) (int64, error) {246 return strconv.ParseInt(groups[0], 10, 64)247 }, td.Catch(&brittID, td.NotZero()))))248 fmt.Printf("POST /person - raw: %t → Britt ID=%d\n", !ta.Failed(), brittID)249 //250 // Get a Person251 //252 ta.Get(fmt.Sprintf("/person/%d", aliceID), "Accept", "application/xml").253 Name("GET Alice - XML (ID #%d)", aliceID).254 CmpStatus(200).255 CmpHeader(contentTypeIs("application/xml")).256 CmpXMLBody(td.SStruct( // using SStruct operator257 Person{258 ID: aliceID,259 Name: "Alice",260 Age: 35,261 },262 td.StructFields{263 "CreatedAt": td.Ptr(td.NotZero()),264 },265 ))266 fmt.Println("GET XML Alice:", !ta.Failed())267 ta.Get(fmt.Sprintf("/person/%d", aliceID), "Accept", "application/json").268 Name("GET Alice - JSON (ID #%d)", aliceID).269 CmpStatus(200).270 CmpHeader(contentTypeIs("application/json")).271 CmpJSONBody(td.JSON(` // using JSON operator (yes comment allowed in JSON!)272{273 "id": $1,274 "name": "Alice",275 "age": 35,276 "created_at": $2277}`,278 aliceID,279 td.Not(td.Re(`^0001-01-01`)), // time is not 0001-01-01… aka zero time.Time280 ))281 fmt.Println("GET JSON Alice:", !ta.Failed())282 //283 // Delete a Person284 //285 ta.Delete(fmt.Sprintf("/person/%d", aliceID), nil).286 Name("DELETE Alice (ID #%d)", aliceID).287 CmpStatus(204).288 CmpHeader(td.Not(td.ContainsKey("Content-Type"))).289 NoBody()290 fmt.Println("DELETE Alice:", !ta.Failed())291 // Check Alice is deleted292 ta.Get(fmt.Sprintf("/person/%d", aliceID), "Accept", "application/json").293 Name("GET (deleted) Alice - JSON (ID #%d)", aliceID).294 CmpStatus(404).295 CmpHeader(contentTypeIs("application/json")).296 CmpJSONBody(td.JSON(`297{298 "message": "Person does not exist",299 "code": 404300}`))301 fmt.Println("Alice is not found anymore:", !ta.Failed())302 // Output:303 // GET /person/42 - JSON: true304 // GET /person/42 - XML: true305 // GET /person/42 - raw: true306 // POST /person - XML: true → Bob ID=1307 // POST /person - JSON: true → Alice ID=2308 // POST /person - raw: true → Britt ID=3309 // GET XML Alice: true310 // GET JSON Alice: true311 // DELETE Alice: true312 // Alice is not found anymore: true313}...

Full Screen

Full Screen

multipart_test.go

Source:multipart_test.go Github

copy

Full Screen

...25 _, err := io.CopyN(&final, part, 5)26 if assert.CmpNoError(err) {27 _, err := io.Copy(&final, part)28 if assert.CmpNoError(err) {29 assert.Cmp(final.String(), strings.ReplaceAll(expected, "%CR", "\r"))30 }31 }32 }33 // Full empty34 b, err := io.ReadAll(&tdhttp.MultipartPart{})35 assert.CmpNoError(err)36 assert.Len(b, 0)37 // Without name38 part := tdhttp.MultipartPart{39 Content: strings.NewReader("hey!\nyo!"),40 }41 check(&part, `Content-Type: text/plain; charset=utf-8%CR42%CR43hey!44yo!`)45 // Without body46 part = tdhttp.MultipartPart{47 Name: "nobody",48 }49 check(&part, `Content-Disposition: form-data; name="nobody"%CR50`)51 // Without header52 part = tdhttp.MultipartPart{53 Name: "pipo",54 Content: strings.NewReader("hey!\nyo!"),55 }56 check(&part, `Content-Disposition: form-data; name="pipo"%CR57Content-Type: text/plain; charset=utf-8%CR58%CR59hey!60yo!`)61 // With header62 part = tdhttp.MultipartPart{63 Name: "pipo",64 Content: strings.NewReader("hey!\nyo!"),65 Header: http.Header{66 "Pipo": []string{"bingo"},67 "Content-Type": []string{"text/rococo; charset=utf-8"},68 },69 }70 check(&part, `Content-Disposition: form-data; name="pipo"%CR71Content-Type: text/rococo; charset=utf-8%CR72Pipo: bingo%CR73%CR74hey!75yo!`)76 // Without name & body, but with header77 part = tdhttp.MultipartPart{78 Header: http.Header{79 "Pipo": []string{"bingo"},80 },81 }82 check(&part, `Pipo: bingo%CR83`)84 // io.Reader85 check(tdhttp.NewMultipartPart("io", strings.NewReader("hey!\nyo!")),86 `Content-Disposition: form-data; name="io"%CR87Content-Type: text/plain; charset=utf-8%CR88%CR89hey!90yo!`)91 // io.Reader + Content-Type92 check(tdhttp.NewMultipartPart("io", strings.NewReader("hey!\nyo!"), "text/rococo; charset=utf-8"),93 `Content-Disposition: form-data; name="io"%CR94Content-Type: text/rococo; charset=utf-8%CR95%CR96hey!97yo!`)98 // String99 check(tdhttp.NewMultipartPartString("pipo", "hey!\nyo!"),100 `Content-Disposition: form-data; name="pipo"%CR101Content-Type: text/plain; charset=utf-8%CR102%CR103hey!104yo!`)105 // String + Content-Type106 check(tdhttp.NewMultipartPartString("pipo", "hey!\nyo!", "text/rococo; charset=utf-8"),107 `Content-Disposition: form-data; name="pipo"%CR108Content-Type: text/rococo; charset=utf-8%CR109%CR110hey!111yo!`)112 // Bytes113 check(tdhttp.NewMultipartPartBytes("pipo", []byte("hey!\nyo!")),114 `Content-Disposition: form-data; name="pipo"%CR115Content-Type: text/plain; charset=utf-8%CR116%CR117hey!118yo!`)119 // Bytes + Content-Type120 check(tdhttp.NewMultipartPartBytes("pipo", []byte("hey!\nyo!"), "text/rococo; charset=utf-8"),121 `Content-Disposition: form-data; name="pipo"%CR122Content-Type: text/rococo; charset=utf-8%CR123%CR124hey!125yo!`)126 // With file name127 dir, err := os.MkdirTemp("", "multipart")128 require.CmpNoError(err)129 defer os.RemoveAll(dir)130 filePath := filepath.Join(dir, "body.txt")131 require.CmpNoError(os.WriteFile(filePath, []byte("hey!\nyo!"), 0666))132 check(tdhttp.NewMultipartPartFile("pipo", filePath),133 `Content-Disposition: form-data; name="pipo"; filename="body.txt"%CR134Content-Type: text/plain; charset=utf-8%CR135%CR136hey!137yo!`)138 // With file name + Content-Type139 check(tdhttp.NewMultipartPartFile("pipo", filePath, "text/rococo; charset=utf-8"),140 `Content-Disposition: form-data; name="pipo"; filename="body.txt"%CR141Content-Type: text/rococo; charset=utf-8%CR142%CR143hey!144yo!`)145 // Error during os.Open146 _, err = io.ReadAll(147 tdhttp.NewMultipartPartFile("pipo", filepath.Join(dir, "unknown.xxx")),148 )149 assert.CmpError(err)150}151func TestMultipartBody(t *testing.T) {152 assert, require := td.AssertRequire(t)153 dir, err := os.MkdirTemp("", "multipart")154 require.CmpNoError(err)155 defer os.RemoveAll(dir)156 filePath := filepath.Join(dir, "body.txt")157 require.CmpNoError(os.WriteFile(filePath, []byte("hey!\nyo!"), 0666))158 for _, boundary := range []struct{ in, out string }{159 {in: "", out: "go-testdeep-42"},160 {in: "BoUnDaRy", out: "BoUnDaRy"},161 } {162 multi := tdhttp.MultipartBody{163 Boundary: boundary.in,164 Parts: []*tdhttp.MultipartPart{165 {166 Name: "pipo",167 Content: strings.NewReader("pipo!\nbingo!"),168 },169 tdhttp.NewMultipartPartFile("file", filePath),170 tdhttp.NewMultipartPartString("string", "zip!\nzap!"),171 tdhttp.NewMultipartPartBytes("bytes", []byte(`{"ola":"hello"}`), "application/json"),172 tdhttp.NewMultipartPart("io", nil),173 tdhttp.NewMultipartPart("", nil),174 },175 }176 expected := `--` + boundary.out + `%CR177Content-Disposition: form-data; name="pipo"%CR178Content-Type: text/plain; charset=utf-8%CR179%CR180pipo!181bingo!%CR182--` + boundary.out + `%CR183Content-Disposition: form-data; name="file"; filename="body.txt"%CR184Content-Type: text/plain; charset=utf-8%CR185%CR186hey!187yo!%CR188--` + boundary.out + `%CR189Content-Disposition: form-data; name="string"%CR190Content-Type: text/plain; charset=utf-8%CR191%CR192zip!193zap!%CR194--` + boundary.out + `%CR195Content-Disposition: form-data; name="bytes"%CR196Content-Type: application/json%CR197%CR198{"ola":"hello"}%CR199--` + boundary.out + `%CR200Content-Disposition: form-data; name="io"%CR201%CR202--` + boundary.out + `%CR203%CR204--` + boundary.out + `--%CR205`206 var final bytes.Buffer207 // Read in 2 times to be sure Read() can be called several times208 _, err = io.CopyN(&final, &multi, 10)209 if !assert.CmpNoError(err) {210 continue211 }212 _, err := io.Copy(&final, &multi)213 if !assert.CmpNoError(err) {214 continue215 }216 if !assert.Cmp(final.String(), strings.ReplaceAll(expected, "%CR", "\r")) {217 continue218 }219 rd := multipart.NewReader(&final, boundary.out)220 // 0221 part, err := rd.NextPart()222 if assert.CmpNoError(err) {223 assert.Cmp(part.FormName(), "pipo")224 assert.Cmp(part.FileName(), "")225 assert.Smuggle(part, io.ReadAll, td.String("pipo!\nbingo!"))226 }227 // 1228 part, err = rd.NextPart()229 if assert.CmpNoError(err) {230 assert.Cmp(part.FormName(), "file")231 assert.Cmp(part.FileName(), "body.txt")232 assert.Smuggle(part, io.ReadAll, td.String("hey!\nyo!"))233 }234 // 2235 part, err = rd.NextPart()236 if assert.CmpNoError(err) {237 assert.Cmp(part.FormName(), "string")238 assert.Cmp(part.FileName(), "")239 assert.Smuggle(part, io.ReadAll, td.String("zip!\nzap!"))240 }241 // 3242 part, err = rd.NextPart()243 if assert.CmpNoError(err) {244 assert.Cmp(part.FormName(), "bytes")245 assert.Cmp(part.FileName(), "")246 assert.Smuggle(part, io.ReadAll, td.String(`{"ola":"hello"}`))247 }248 // 4249 part, err = rd.NextPart()250 if assert.CmpNoError(err) {251 assert.Cmp(part.FormName(), "io")252 assert.Cmp(part.FileName(), "")253 assert.Smuggle(part, io.ReadAll, td.String(""))254 }255 // 5256 part, err = rd.NextPart()257 if assert.CmpNoError(err) {258 assert.Cmp(part.FormName(), "")259 assert.Cmp(part.FileName(), "")260 assert.Smuggle(part, io.ReadAll, td.String(""))261 }262 // EOF263 _, err = rd.NextPart()264 assert.Cmp(err, io.EOF)265 }266 multi := tdhttp.MultipartBody{}267 td.Cmp(t, multi.ContentType(), `multipart/form-data; boundary="go-testdeep-42"`)268 td.Cmp(t, multi.Boundary, "go-testdeep-42",269 "Boundary field set with default value")270 td.CmpEmpty(t, multi.MediaType, "MediaType field NOT set")271 multi.Boundary = "BoUnDaRy"272 td.Cmp(t, multi.ContentType(), `multipart/form-data; boundary="BoUnDaRy"`)273 multi.MediaType = "multipart/mixed"274 td.Cmp(t, multi.ContentType(), `multipart/mixed; boundary="BoUnDaRy"`)...

Full Screen

Full Screen

q_test.go

Source:q_test.go Github

copy

Full Screen

...10 "github.com/maxatome/go-testdeep/helpers/tdhttp"11 "github.com/maxatome/go-testdeep/td"12)13type qTest1 struct{}14func (qTest1) String() string { return "qTest1!" }15type qTest2 struct{}16func (*qTest2) String() string { return "qTest2!" }17func TestQ(t *testing.T) {18 q := tdhttp.Q{19 "str1": "v1",20 "str2": []string{"v20", "v21"},21 "int1": 1234,22 "int2": []int{1, 2, 3},23 "uint1": uint(1234),24 "uint2": [3]uint{1, 2, 3},25 "float1": 1.2,26 "float2": []float64{1.2, 3.4},27 "bool1": true,28 "bool2": [2]bool{true, false},29 }30 td.Cmp(t, q.Values(), url.Values{31 "str1": []string{"v1"},32 "str2": []string{"v20", "v21"},33 "int1": []string{"1234"},34 "int2": []string{"1", "2", "3"},35 "uint1": []string{"1234"},36 "uint2": []string{"1", "2", "3"},37 "float1": []string{"1.2"},38 "float2": []string{"1.2", "3.4"},39 "bool1": []string{"true"},40 "bool2": []string{"true", "false"},41 })42 // Auto deref pointers43 num := 12344 pnum := &num45 ppnum := &pnum46 q = tdhttp.Q{47 "pnum": pnum,48 "ppnum": ppnum,49 "pppnum": &ppnum,50 "slice": []***int{&ppnum, &ppnum},51 "pslice": &[]***int{&ppnum, &ppnum},52 "array": [2]***int{&ppnum, &ppnum},53 "parray": &[2]***int{&ppnum, &ppnum},54 }55 td.Cmp(t, q.Values(), url.Values{56 "pnum": []string{"123"},57 "ppnum": []string{"123"},58 "pppnum": []string{"123"},59 "slice": []string{"123", "123"},60 "pslice": []string{"123", "123"},61 "array": []string{"123", "123"},62 "parray": []string{"123", "123"},63 })64 // Auto deref interfaces65 q = tdhttp.Q{66 "all": []any{67 "string",68 -1,69 int8(-2),70 int16(-3),71 int32(-4),72 int64(-5),73 uint(1),74 uint8(2),75 uint16(3),76 uint32(4),77 uint64(5),78 float32(6),79 float64(7),80 true,81 ppnum,82 (*int)(nil), // ignored83 nil, // ignored84 qTest1{},85 &qTest1{}, // does not implement fmt.Stringer, but qTest does86 // qTest2{} panics as it does not implement fmt.Stringer, see Errors below87 &qTest2{},88 },89 }90 td.Cmp(t, q.Values(), url.Values{91 "all": []string{92 "string",93 "-1",94 "-2",95 "-3",96 "-4",97 "-5",98 "1",99 "2",100 "3",...

Full Screen

Full Screen

String

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m := minify.New()4 m.AddFunc("text/css", css.Minify)5 m.AddFunc("text/html", html.Minify)6 m.AddFunc("text/javascript", js.Minify)7 m.AddFunc("application/json", json.Minify)8 m.AddFunc("image/svg+xml", svg.Minify)9 m.AddFuncRegexp(regexp.MustCompile("[/+]xml$"), xml.Minify)10 err := m.Minify("text/html", os.Stdout, os.Stdin)11 if err != nil {12 log.Fatal(err)13 }14}

Full Screen

Full Screen

String

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m := minify.New()4 m.AddFunc("text/html", html.Minify)5 fmt.Println(m.String("text/html", "<html><head><title>Test</title></head><body><h1>Hello, world!</h1></body></html>"))6}

Full Screen

Full Screen

String

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 m := minify.New()4 m.AddFunc("text/javascript", js.Minify)5 m.Add("text/javascript", &js.Minifier{6 })

Full Screen

Full Screen

String

Using AI Code Generation

copy

Full Screen

1import (2func main() {3 t := tdhttp.Test{4 }5 fmt.Println(t)6}

Full Screen

Full Screen

Automation Testing Tutorials

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.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful